RFC: Pipes: Pluggable Change Detection in Angular 2

113 views
Skip to first unread message

Jeff Cross

unread,
Apr 23, 2015, 4:51:25 PM4/23/15
to angular-...@googlegroups.com
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:

interface Pipe {
    
// Get a change detector ref instance. This can be used to tell change detector to check this path on its next digest, 
    
// if a component opts into ON_PUSH changes only
    constructor
(ref:ChangeDetectorRef);
    
// Cleanup, such as dispose of an observable
    onDestroy
(): void;
    
// Can this pipe do anything with the provided input?
    supports
(val): boolean;
    
// Return a value the change detector can understand, or NO_CHANGE
    transform
(val): any;
}


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?

netanelgilad

unread,
Apr 29, 2015, 1:53:57 PM4/29/15
to angular-...@googlegroups.com
Hey guys,
I'm new here so I'll introduce myself. I'm Netanel and I work with Uri on angular-meteor. Thanks for bringing me in :)

I tried playing a bit with angular 2 and meteor in order to see how we could achieve the things we have today in angular-meteor with angular 2.
I haven't found a lot of material to get to know angular 2 so I may be commenting without having real knowledge on what I'm talking about ;)
I did read Victor's post about change detection in angular 2.

So one question I have about the "async" Pipe is what would happen if instead of doing the check for lastestValue and lastestReturnedValue inside the Pipe, it would just return the latestValue. Would the change detector do a dirty check itself? Is the idea of the Pipe to delegate the dirty checking from the change detector to the Pipe or is it more for unwrapping and transforming data for the change detector to have an easier time dirty-checking it? 

A point about the use of Pipes. Let's say I write a Pipe for our Meteor.Collection type, then I have to get angular-meteor users to use my Pipe on every binding to a Meteor.Collection. I would rather have a mechanism for telling angular: this is how to handle bindings to a Meteor.Collection, and do it once inside of my library. This would mean my users don't need to care how I make angular understand Meteor.Collections.

I hope my comments make sense...

Jeff Cross

unread,
May 7, 2015, 6:42:08 PM5/7/15
to angular-...@googlegroups.com, netane...@gmail.com
Thanks for your comments! Some replies inline.


On Wednesday, April 29, 2015 at 10:53:57 AM UTC-7, netanelgilad wrote:
Hey guys,
I'm new here so I'll introduce myself. I'm Netanel and I work with Uri on angular-meteor. Thanks for bringing me in :)

I tried playing a bit with angular 2 and meteor in order to see how we could achieve the things we have today in angular-meteor with angular 2.
I haven't found a lot of material to get to know angular 2 so I may be commenting without having real knowledge on what I'm talking about ;)
I did read Victor's post about change detection in angular 2.

So one question I have about the "async" Pipe is what would happen if instead of doing the check for lastestValue and lastestReturnedValue inside the Pipe, it would just return the latestValue. Would the change detector do a dirty check itself? Is the idea of the Pipe to delegate the dirty checking from the change detector to the Pipe or is it more for unwrapping and transforming data for the change detector to have an easier time dirty-checking it? 

The Change Detector is still responsible for dirty checking. The pipe can tell it to bypass dirty checking. Your latter understanding is correct, the pipe unwraps/transforms, can optionally let the change detector know if it should skip dirty checking.
 

A point about the use of Pipes. Let's say I write a Pipe for our Meteor.Collection type, then I have to get angular-meteor users to use my Pipe on every binding to a Meteor.Collection. I would rather have a mechanism for telling angular: this is how to handle bindings to a Meteor.Collection, and do it once inside of my library. This would mean my users don't need to care how I make angular understand Meteor.Collections.

Pipes can be used imperatively inside of JavaScript code as well, so you could create an angular-meteor service or directive that would implicitly pipe values through your pipe.
Reply all
Reply to author
Forward
0 new messages