惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
M
MIT News - Artificial intelligence
Engineering at Meta
Engineering at Meta
云风的 BLOG
云风的 BLOG
博客园 - 叶小钗
Jina AI
Jina AI
Last Week in AI
Last Week in AI
The Cloudflare Blog
博客园 - 【当耐特】
Stack Overflow Blog
Stack Overflow Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
WordPress大学
WordPress大学
博客园_首页
I
InfoQ
G
Google Developers Blog
Martin Fowler
Martin Fowler
Recent Announcements
Recent Announcements
H
Help Net Security
U
Unit 42
Blog — PlanetScale
Blog — PlanetScale
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
IT之家
IT之家
Microsoft Security Blog
Microsoft Security Blog

博客园 - 风轻如梦

阿里云专属推荐码nuyxa6 WHY JAVASCRIPT NEEDS TYPES BUILDING ANGULAR APPS USING FLUX ARCHITECTURE TWO PHASES OF ANGULAR 2 APPLICATIONS Forms in Angular 2 BETTER SUPPORT FOR FUNCTIONAL PROGRAMMING IN ANGULAR 2 ANGULAR 2 BITS: UNIFIED DEPENDENCY INJECTION ddd http://angular.github.io/router/ background image http://4526621.blog.51cto.com/4516621/1343369 http://www.atool.org/keytype.php#0-tsina-1-53371-397232819ff9a47a7b7e80a40613cfe1 yahoo 待做 qiangchezu car contact qq 30288891@qq.com http://depositphotos.com/ together 矢量图 耳石症
CHANGE DETECTION IN ANGULAR 2
风轻如梦 · 2015-04-08 · via 博客园 - 风轻如梦

In this article I will talk in depth about the Angular 2 change detection system.

HIGH-LEVEL OVERVIEW

An Angular 2 application is a tree of components.

Tree of components

An Angular 2 application is a reactive system, with change detection being the core of it.

A reactive system

Every component gets a change detector responsible for checking the bindings defined in its template. Examples of bindings:{{todo.text}} and [todo]=”t”. Change detectors propagate bindings[c] from the root to leaves in the depth first order.

In Angular 2 there are no two-way data-bindings. That is why the change detection graph is a directed tree and cannot have cycles (i.e., it is an arborescence). This makes the system significantly more performant. And what is more important we gain guarantees that make the system more predictable and easier to reason about. How Fast Is It?

By default the change detection goes through every node of the tree to see if it changed, and it does it on every browser event. Although it may seem terribly inefficient, the Angular 2 change detection system can go through hundreds of thousands of simple checks (the number are platform dependent) in a few milliseconds. How we managed to achieve such an impressive result is a topic for another blog post.

Angular has to be conservative and run all the checks every single time because the JavaScript language does not give us object mutation guarantees. But we may know that certain properties hold if we, for example, use immutable or observable objects. Previously Angular could not take advantage of this, now it will.

IMMUTABLE OBJECTS

If a component depends only on its bindings, and the bindings are immutable, then this component can change if and only if one of its bindings changes. Therefore, we can skip the component’s subtree in the change detection tree until such an event occurs. When it happens, we can check the subtree once, and then disable it until the next change (gray boxes indicate disabled change detectors).

A disabled change detector

If we are aggressive about using immutable objects, a big chunk of the change detection tree will be disabled most of the time.

A disabled tree

Implementing this is trivial.

class ImmutableTodoCmp {
  todo:Todo;
  constructor(private bingings:BindingPropagationConfig){}
  // onChange is a part of the component’s lifecycle. 
  

The framework can make it even easier to use through a mixin.

class ImmutableTodoCmp {
  todo:Todo;
}
applyMixins(ImmutableTodoCmp, [ComponentWithImmutableBindings]);

OBSERVABLE OBJECTS

If a component depends only on its bindings, and the bindings are observable, then this component can change if and only if one of its bindings emits an event. Therefore, we can skip the component’s subtree in the change detection tree until such an event occurs. When it happens, we can check the subtree once, and then disable it until the next change.

Although it may sound similar to the Immutable Objects case, it is quite different. If you have a tree of components with immutable bindings, a change has to go through all the components starting from the root. This is not the case when dealing with observables.

Let me sketch out a small example demonstrating the issue.

type ObservableTodo = Observable<Todo>;
type ObservableTodos = Observable<Array<ObservableTodo>>;

@Component({selector:’todos’})
class ObservableTodosCmp {
  todos:ObservableTodos;
  //...
}

The template of ObservableTodosCmp:

<todo template=”ng-repeat: var t in todos” todo=”t”></todo>

Finally, ObservableTodoCmp:

@Component({selector:’todo’})
class ObservableTodoCmp {
  todo:ObservableTodo;
  

As you can see, here the Todos component has only a reference to an observable of an array of todos. So it cannot see the changes in individual todos.

The way to handle it is to check the path from the root to the changed Todo component when its todo observable fires an event. The change detection system will make sure this happens.

Say our application uses only observable objects. When it boots, Angular will check all the objects.

Change detectors are enabled

So the state after the first pass will look as follows.

Change detectors are disabled

Let’s say the first todo observable fires an event. The system will switch to the following state:

A path is enabled

And after checking App_ChangeDetector, Todos_ChangeDetector, and the first Todo_ChangeDetector, it will go back to this state.

Change detectors are disabled

Assuming that changes happen rarely and the components form a balanced tree, using observables changes the complexity of change detection from O(N) to O(logN), where N is the number of bindings in the system.

Angular will come with mixins for the most popular libraries and Object.observe.

class ObservableTodoCmp {
  todo:ObservableTodo;
}
applyMixins(ImmutableTodoCmp, [ComponentWithRxObservableBindings]);

But this capability is not tied to those libraries. Implementing such a mixin for another library should be a matter of a few lines of code.

DO OBSERVABLES CAUSE CASCADING UPDATES?

Observable objects have bad reputation because they can cause cascading updates. Anyone who has experience in building large applications in a framework that relies on observable models knows what I am talking about. One observable object update can cause a bunch of other observable objects trigger updates, which can do the same. Somewhere along the way views get updated. Such systems are very hard to reason about.

Using observable objects in Angular 2 as shown above will not have this problem. An event triggered by an observable just marks a path from the component to the root as to be checked next time. Then the normal change detection process kicks in and goes through the nodes of the tree in the depth first order. So the order of updates does not change whether you use observables or not. This is very important. Using an observable object becomes a simple optimization that does not change the way you think about the system.

DO I HAVE TO USE OBSERVABLE/IMMUTABLE OBJECTS EVERYWHERE TO SEE THE BENEFITS?

No, you don’t have to. You can use observables in one part of your app (e.g., in some gigantic table.), and that part will get the performance benefits. Even more, you can compose different types of components and get the benefits of all of them. For instance, an “observable component” can contain an “immutable component”, which can itself contain an “observable component”. Even in such a case the change detection system will minimize the number of checks required to propagate changes.

NO SPECIAL CASES

Support for immutable and observable objects is not baked into the change detection. These types of components are not in any way special. So you can write your own directives that use change detection in a “smart” way. For instance, imagine a directive updating its content every N seconds.

SUMMARY

  • An Angular 2 application is a reactive system.
  • The change detection system propagates bindings from the root to leaves.
  • Unlike Angular 1.x, the change detection graph is a directed tree. As a result, the system is more performant and predictable.
  • By default, the change detection system walks the whole tree. But if you use immutable objects or observables, you can take advantage of them and check parts of the tree only if they “really change”.
  • These optimizations compose and do not break the guarantees the change detection provides.

API CHANGES

This described functionality is already in the Angular 2 master branch on GitHub. The current API, however, may change before the first stable release of the framework.

    • So Angular 2 basically uses the Flux Architecture? :-)

    •  
    • Reply
  • Avatar

    Foo • 2 months ago

    Interesting stuff, thanks! One question: how exactly do observables avoid cascading updates? My understanding is that in Angular 1.x, cascading updates are dealt with by repeating the $digest() loop until no further changes are detected. Won't that still be true? Thanks to the new immutable and observable features you outline, each loop will have less to process, but it still needs to repeat until a stable state is reached, right?

  •  
  • Reply
    • Avatar

      Victor Savkin Mod  Foo • 2 months ago

      Thanks.

      In Angular2 we walk the change detection graph only once (TTL = 1). This is possible because the graph does not have any cycles.

      What I meant by cascading updates is the following:

      Say we have three observable objects: ObjectA, ObjectB, ObjectC. Consider the following situation.
      - ObjectA fires an event
      - The view rendering ObjectA catches the event and rerenders
      - ObjectB catches the event and fires its own event
      - The view rendering ObjectB catches the event and rerenders
      - ObjectC catches the event and fires its own event
      - ObjectA catches the event fired by ObjectC
      ...

      Note how the framework calls (view.render) are interleaved with the events. It is hard to tell what gets rerendered when. This is a real problem of many front-end frameworks.

      In Angular2 it works as follows.
      - ObjectA fires an event
      - ObjectB catches the event and fires its own event
      - ObjectC catches the event and fires its own event
      - ObjectA catches the event fired by ObjectC

      While it is happening the framework is marking the parts of the change detection graph that are updated. This marking has no observable side effects. At the end of the VM turn the framework will check only the marked paths and update the bindings.

      Note that there is a clear separation of when the model is updated and when the framework does its magic. The bindings would be updated in the same order if ObjectA, ObjectB, and ObjectC were not observable. This is very important.

      see more

    •  
    • Reply
  • Avatar

    Nate • 2 months ago

    > bingings.shouldBePropagated();

    Not so sure about propagating bingings!!!

  •  
  • Reply
  • Avatar

    Csaba Tamas • 15 days ago

    How can I manual run change detection or subscripe it for example in component.js
    this.service.ajaxCall((result) => {
    this.todos = result.todos; //change
    this.findNewElementInDOM(); // dont't work
    --> Don't runned the change detection here, but I only can here cach the async call and detect change
    });
    --> change detection only run after ajaxCall callback executed

  •  
  • Reply
  • Avatar

    ajaybeniwal • 19 days ago

    Its nearly similar to React and Flux using immutable.js along with it

  •  
  • Reply
  • Avatar

    Manfred Steyer • a month ago

    I'm wondering, when the digest-phase is triggered. You mentioned that it's triggered on every browser-event. But I've noticed, that the view is even updated, when I update the bindings in an async success-handler, angular2 isn't aware of. As I don't use observables this seems quite magic to me ...

  •  
  • Reply
  • Avatar

    seba • a month ago

    So, how does it differ from React? It looks to be almost the same now... Well, except they've invented their own language now, Hooray :( Sure you can do it in plain JS, but then you end up with the same mess as in Angular 1. Moving stuff out of your api and into annotations, doesn't make your api better.

    It seems angular 2 will loose a huge amount of convenience for the developer in trade for performance, while the angular folks been preaching angular 1 didn't have a performance problem because you never need to show more than 2000 bound items at once etc. And to be honest, angular 1 is performing just fine and I'd rather keep my speed of development than get performance I don't need. If people need it, let them use React. Now we seem to loose the choice and get 2 times React.

  •  
  • Reply
    • Avatar

      Manfred Steyer  seba • a month ago

      While I see this point just like you, I hope, that there will be some syntax sugar, that will bring back the convenience from 1.x to 2.x ...

    •  
    • Reply
  • Avatar

    kowdermeister • a month ago

    It seems it's safe to conclude that Angular apps are not written anymore in JavaScript, but in Angular language. Probably this will never make it popular.

  •  
  • Reply
  • Avatar

    Jimmy Breck-McKye • 2 months ago

    Forgive me, but didn't Knockout.js effectively implement this four years ago?

  •  
  • Reply
    • Avatar

      Victor Savkin Mod  Jimmy Breck-McKye • 2 months ago

      If you are referring to the notion of Observables, then, yes, most frameworks support observable models. Most frameworks, however, require you to use a particular kind of observables and suffer from cascading updates.

      Angular does not force you to use one kind of observables: you can use rxjs, object.observe, backbone models, probably even knockout models. It can take advantage of any of them because the described mechanism is more generic.

      The most important thing is that you don't have to use observables at all and instead take advantage of immutable objects. The framework remains model agnostic.

    •  
    • Reply
      • Avatar

        Jimmy Breck-McKye  Victor Savkin • 2 months ago

        Knockout does not require you to use observables if a model property does not mutate; it will quite happily bind plain properties - it just won't react to mutations. You can even place computed properties in front of multiple observables, and Knockout will only update the computed's subscribers when the computed's result actually changes.

        Knockout provides a great deal of fine control over how mutation propagates; the utils it provides make it easy to write performant code naively. I think it's a real shame that so many developers dismiss it without even looking at it.

      •  
      • Reply
      • Avatar

        johnpapa7  Victor Savkin • 2 months ago

        This is an important statement here: "The most important thing is that you don't have to use observables at all and instead take advantage of immutable objects."

        One of the biggest draws to Angular 1.x was the idea of not requiring an implementation of wrapping objects with observability. We could use raw javascript objects. Anyone who has done this in Knockout or other similar observable tech (IObservable in .NET even) knows the cost of it is you always have to convert your objects (or wrap them) with this feature. Can you clarify your statement here on how immutable objects can be used, say, given a todo:

        [
        { id: 1, description: 'go shopping'},
        {id: 2, description: 'take kids to school'}
        ]

      •  
      • Reply
        • Avatar

          Victor Savkin Mod  johnpapa7 • 2 months ago

          There are three problems with using immutable objects in Angular 1.x:
          - Angular does not know that they are immutable and keeps checking them. So it is not efficient.
          - Custom immutable collections do not work with NgRepeat out of the box .
          - NgModel does not play nicely with immutable objects.

          Angular2 solves all of them.

          - The change detection gives you a mechanism to express that the component depends only on immutable objects and, therefore, should not be checked until one of the bindings gets updated, as follows:

          // There will be a way to express it declaratively
          class ImmutableTodoCmp {
          todo:Todo;
          constructor(private bingings:BindingPropagationConfig){}
          onChange(_) => bingings.shouldBePropagated(); 
          }

          - The change detection is pluggable, so you can teach it to detect changes in any collection.
          - The new Forms API is immutable object friendly.

          In Angular2 using immutable objects feels natural. You can pass an immutable collection to a repeater.

          <todo !foreach="var t in todos" [todo]="t">

          Use an immutable object in your template

          {{todo.description}}

          Even though Angular2 "just works", you will have to change the way your app is architected if you make most of your models immutable. This is not Angular specific. This will be true in any framework. That is why there are such things as Flux and Cursors (https://github.com/omcljs/om/w....

          see more

        •  
        • Reply
          • Avatar

            johnpapa7  Victor Savkin • 2 months ago

            It appears that json objects will have to be converted to an object of this type and cannot be used as is. Unless I misunderstand. Right?

            We may gain Perf but we lose something big there.

          •  
          • Reply
            • Avatar

              Victor Savkin Mod  johnpapa7 • 2 months ago

              Sorry, I was not clear. We can define Todo in TypeScript as follows:

              interface Todo {
              description:string;
              checked:boolean;
              }

              So Todo just defines the shape of the object. It can be a regular JSON object with the two fields.

            •  
            • Reply
  • Avatar

    Sekib Omazic • 2 months ago

    Will A2 apply DOM changes all at once (batch update) like ReactJS? I couldn't find something like "task queue" in the code base.

  •  
  • Reply
  • Avatar

    Sekib Omazic • 2 months ago

    So to use immutable objects I have to inform angular2 about it by implementing onChange method in the component? And if I want to use observables I'd have to use objects of type Observable? 
    A working TodoApp would help better understand the change detection in A2.

  •  
  • Reply
    • Avatar

      Victor Savkin Mod  Sekib Omazic • 2 months ago

      >> So to use immutable objects I have to inform angular2 about it by implementing onChange method in the component?

      You can use immutable and observable objects without telling Angular about it, but then you won't get any performance benefits out of it. To get the performance benefits you need to tell the framework that you depend only on immutable or observable objects.

      >> And if I want to use observables I'd have to use objects of type Observable?

      You won't have to use any special Observable class. Any push-based object can be used here (e.g., Object.observe or Rx observables). The framework does not make any assumptions about the type of your observables. Likewise, the framework does not make any assumptions about what is immutable.

      >> A working TodoApp would help better understand the change detection in A2.

      I am putting something together that will show advanced scenarios of using the change detection.

    •  
    • Reply
      • Avatar

        Badru  Victor Savkin • 2 months ago

        Waiting for that "something together" victor. It will help to understand better.
        One more question, the above syntax is not native js. Is it AtScript syntax?