• CHANGE DETECTION IN ANGULAR 2


    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. 
      // It is called when any the bindings changes.
      onChange(_) => bingings.shouldBePropagated(); 
    }
    

    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

        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

          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

        > bingings.shouldBePropagated();

        Not so sure about propagating bingings!!!

      •  
      • Reply
      •  
         
      •  
         
        Avatar

        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

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

      •  
      • Reply
      •  
         
      •  
         
        Avatar

        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

        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

        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

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

      •  
      • Reply
      •  
         
        •  
           
          Avatar

          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

            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

            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

              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

                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

                  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

        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

        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

          >> 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

            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?

  • 相关阅读:
    storm中DAU实时计算方案
    冒泡排序
    跨域
    关于java面试题
    vue+npm+Element插件+路由
    Android云端APP
    js图片预览带进度条
    jQuery上传文件显示进度条
    SSM+form表单文件上传
    SSM批量添加数据
  • 原文地址:https://www.cnblogs.com/eebb/p/4402391.html
Copyright © 2020-2023  润新知