I wanted to put forward a new API called "Pipes" for comments by this group. Victor Savkin has been the thought-leader and implementor of this feature. Jafar Husain has already worked with this API some, and I talked with Uri Goldshtein (angular-meteor) about this API in March when he was in SF. I'd like feedback from others working on data libraries and services regarding ways this API could be improved.
Pipes are part of a general effort to make the Angular 2 core friendly to different ways that developers get data into their applications. I'll post some other designs here in due time.
Pipes are responsible for taking an input provided in a template, and transforming it in a way that can be understood by the change detector. They also can perform tasks when the scope is being destroyed. They're defined in JavaScript, and used in templates. Here is the interface of a pipe:
The usage of pipes in templates is similar to how filters are used in Angular 1:
<span>{{myObservable | async}}</span>
Pipes are different from filters in a couple of ways, particularly that they can be stateful if needed (i.e. to cache inputs and values), and are designed not only to transform data for a view, but also to transform data for the change detector, and can enable optimizations such as avoiding dirty checking.
Practical applications for pipes could include unwrapping and caching promises:
<span>{{ usersPromise | promise }}</span>
listening/unlistening to
child_added event on a firebase reference:
<span>{{ chatMessages | firebaseArray }}</span>
A good pipe example is the "
async" pipe that Victor created, which takes the boilerplate out of binding observables to views. Let's walk through how it works.
supports(obs):boolean {
return ObservableWrapper.isObservable(obs);
}
The "supports" method is called whenever the input to the pipe changes. In Angular 2, we create wrappers for all constructs that transpile to JS or Dart differently, such as Arrays, Promises, Observables, DOM APIs. This implementation of supports() uses Angular's ObservableWrapper to know if the provided value is an observable, and returns true or false when the change detector is determining if this pipe can be used for the provided input. If the input changes in such a way that the pipe can no longer support it, the pipe gets destroyed implicitly.
onDestroy():void {
if (isPresent(this._subscription)) {
this._dispose();
};
}
When the pipe is destroyed, it will automatically dispose of the subscription.
transform(obs:Observable):any {
if (isBlank(this._subscription)) {
this._subscribe(obs);
return null;
}
if (obs !== this._observable) {
this._dispose();
return this.transform(obs);
}
if (this._latestValue === this._latestReturnedValue) {
return NO_CHANGE;
} else {
this._latestReturnedValue = this._latestValue;
return this._latestValue;
}
}
The transform method will set up the observable subscription the first time it's called, will dispose of previous observable description if the passed-in observable doesn't match the cached observable, and will return the latest value (or NO_CHANGE constant if value hasn't changed since last time transform was called). This is the meaty method of the pipe, called every time the change detector cycles.
Note: pipes are called by the change detector, the only call they can make to the change detector is "requestCheck()," which only comes into play for Components that have opted in to push-only change detection of all values. For example, in the async pipe, every time a new value is pushed to the observable, a call to request check is made. Change detection is automatically executed in response to zone-intercepted events, such as DOM events, XHR events, Web Socket events, Web Worker events, so there's less of a need for developers to think about digests as in Angular 1.
What do you think of this approach? What else would you wish for to have more control over change detection?