3

i 've been playing around with AngularJS 1.x and Angular 2, trying to compare their performance.

Here is a Plunkr showing a 'down-side' with Angular 1.x. If too many elements are present on the scope, you will notice lags in rendering the input field as you edit it, since the framework will check all elements on the scope each time it detects an event that could have changed any.

Excerpt from the first Plunkr (html):

<body ng-app="myApp">
<div ng-controller="myCtrl">
  <input ng-model ="name"></input>
  Hello, {{name}}!
  <button ng-click="generateFields()">
    Generate 5000 elements
  </button>
  {{list}}
</div>

Excerpt from the first Plunkr (js):

myApp.controller('myCtrl', function($scope) {
$scope.name = 'name';
$scope.list = [];

$scope.generateFields = function(){
    for(i=0; i<5000;i++){
    $scope.list.push(i);
  }
}

});

In this Plunkr, i 've written a similar sample in Angular 2. It seems that there is no lag at all. How is this resolved in Angular 2? Does the framework somehow knows that only the input field is changed, or is it just faster in performing dirty checking because of the VM optimized change detectors?

Excerpt from the second Plunkr:

@Component({
 selector: 'my-app',
  providers: [],
  template: `
    <div>
      <div>{{myProp}}</div>
      <input [(ngModel)]="myProp" />
      <button (click)="generateFields()">Generate</button>
      <div>{{myList}}</div>
    </div>
  `,
  directives: []
})
export class App {
  constructor() {
  }

  myProp :string = "Change me!";
  myList :any = [];

  generateFields(){
     for (var i = 1; i < 5000; i++)
     {       
          this.myList.push(i);
     }

    console.log("fields generated");
  }
}
Fjut
  • 1,314
  • 12
  • 23
  • Did you enable prodMode? (can't open Plunker on the phone) and you get another 100% boost. – Günter Zöchbauer Jun 05 '16 at 19:04
  • Because the change detection will run only once in prodmode? I haven't tried it because I haven't noticed any lags in the angular 2 example, no matter how many elements are added to the list. – Fjut Jun 05 '16 at 19:41
  • Exactly. Angular2 CD is extremly efficient because how it uses zones and the split of two-way-binding in bindings and events. You can further optimize using `ChangeDetectionStrategy.OnPush` – Günter Zöchbauer Jun 05 '16 at 19:52
  • Yes, i especially like the fact that CD is customizable, giving us much more control over when and how it is performed. However, i am still not sure where is the difference in performance in the two examples above coming from. – Fjut Jun 05 '16 at 20:25
  • I don't know Angular 1.x, therefore I can't tell why it is slower. Angular2 only runs change detection when a subscribed event was fired or an async call was completed. Angular also doesn't compare contents of objects or array, it does only `===` checks. – Günter Zöchbauer Jun 06 '16 at 04:24
  • If this is the case that angular does only === checks, how does it know that it needs to propagate the change to DOM after you've clicked on the Generate button in the second plunkr? Elements are added to the array but the reference of the array object is not changed. – Fjut Jun 06 '16 at 10:58
  • I think it's because `
    {{myList}}
    ` binds to `myList.toString()` and at every change detection cycle (for example after button click) it compares the result which is different when it contains different values. If you for example did instead ``, nothing would happen when `ChildComponent` doesn't bind to `{{myList}}`
    – Günter Zöchbauer Jun 06 '16 at 11:06
  • Thanks, i got it now. Please add your explanation as aswer so that i can accept it. – Fjut Jun 06 '16 at 16:57

2 Answers2

4

Exactly. Angular2 CD is extremely efficient because how it uses zones and the split of two-way-binding in bindings and events. You can further optimize using ChangeDetectionStrategy.OnPush

Because the change detection will run only once in prodmode? I haven't tried it because I haven't noticed any lags in the angular 2 example, no matter how many elements are added to the list.

I don't know Angular 1.x, therefore I can't tell why it is slower. Angular2 only runs change detection when a subscribed event was fired or an async call was completed. Angular also doesn't compare contents of objects or array, it does only === checks.

If this is the case that angular does only === checks, how does it know that it needs to propagate the change to DOM after you've clicked on the Generate button in the second plunkr? Elements are added to the array but the reference of the array object is not changed.

I think it's because <div>{{myList}}</div> binds to myList.toString() and at every change detection cycle (for example after button click) it compares the result which is different when it contains different values. If you for example did instead <child-comp [data]="myList">, nothing would happen when ChildComponent doesn't bind to {{myList}}.

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I just realized what is happening. Please have a look at the second plunkr, i just updated it. Now it has a child component which gets the list as an input parameter. As soon as I introduced that, Angular was forced to do a deep check of the array because of the default CD strategy, meaning that performance lowered significantly as soon as you generate enough elements. It is still significanlty faster than angular 1 though. Bottom line, input parameters are deeply checked for changes in case of a default CD strategy but component 'local' variables are not. – Fjut Jun 06 '16 at 18:17
  • As mentioned I'm pretty sure it's just caused by the `.toString()`. Of course this takes longer with more elements but Angular doesn't do any deep compairson by itself. – Günter Zöchbauer Jun 06 '16 at 18:35
  • I think that you are mistaken here. If that would be the case, we would not have need for a OnPush strategy and immutables, as that would already be covered by the default strategy. Please refer to this [article](http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html), especially the part Undestanding mutability. In there, it is specifically stated that Angular deeply checks input objects and their properties by default. – Fjut Jun 06 '16 at 18:54
  • I'm sure it doesn't. OnPush is to only do change detection when invoked "manually". I read the article a while back. I now just searched it for "deep" and there was only one unrelated occurence. Can you point me to the section where you found it so I don't have to read it all. – Günter Zöchbauer Jun 06 '16 at 18:58
  • In the part Understanding mutability - the paragraph that starts with ''The important part is that changeData() ..." and the next two paragraphs. It is explained that Angular actually check the properties of the object in question, since the reference is the same. – Fjut Jun 06 '16 at 19:47
  • 1
    I think I see what you mean. It's not very clear and I think it's just an unfortunate formulation to make a point for immutability. Angular CD doesn't check the contents and properties of arrays and objects, except when they are used explicitly in binding expressions. This is why observables are sugested everywhere, because they notify actively about changes without involving CD. – Günter Zöchbauer Jun 06 '16 at 20:01
  • Yes, and since Property binding is another binding expresion, it makes Angular to do deep checking by default. I think it is cleary visible in the second plunker, as soon as you generate a large number of elements. And it clearly show that Angular 2 dirty checking mechanism is much faster that the previous one, as it takes much more elements to start noticing lags. – Fjut Jun 06 '16 at 20:41
1

Is it just faster in performing dirty checking because of the VM optimized change detectors?

It is hard to say, since change detection in Angular 2 is completely different from Angular 1. I think the only thing that is the same is the (logical) concept of dirty checking template bindings. Most likely it is the monomorphic (VM-friendly/optimized) code – Angular blog, thoughtram blog, V.Savkin talk – that Angular 2 generates in the change detector objects that it creates for each component.

Please have a look at the second plunkr, i just updated it. Now it has a child component which gets the list as an input parameter. As soon as I introduced that, Angular was forced to do a deep check of the array because of the default CD strategy, meaning that performance lowered significantly as soon as you generate enough elements. It is still significanlty faster than angular 1 though. Bottom line, input parameters are deeply checked for changes in case of a default CD strategy but component 'local' variables are not.

... and since Property binding is another binding expression, it makes Angular to do deep checking by default.

If a template binding contains something that is iterable – e.g., [myList]="myList" – then in development mode only, change detection actually iterates through all of the (e.g. myList) items and compares them, even if there isn't an NgFor loop or something else that creates a template binding to each element in the child component. This very different from the looseIdentical() check (i.e., === check, hence reference check) that is performed in production mode. For very large iterables, this could have a performance impact, in development mode only, as you discovered.

Please see https://stackoverflow.com/a/37356950/215945 for a more in-depth discussion related to this "devMode only deep checking".

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Does this mean that, in the example from second plunkr, in prodmode, for default CD strategy, even if the looseIdentical() returns true, CD will still run for the child component? – Fjut Jun 08 '16 at 06:29
  • Another question, how does Angular know that it needs to update the DOM when we use interpolation and how is that so fast? Is it, as @Gunter mentioned, because {{myList}} evaluates to myList.ToString() so Angular actually compares strings instead of looping through the array? – Fjut Jun 08 '16 at 06:32
  • 1
    @Fjut, by default (i.e., you're not using `OnPush`), in devMode and prodMode, CD will dirty-check every template binding in every component (how it does the dirty checking depends on the mode). Even if an input property's reference hasn't changed (e.g., `myList` still references the same array), the template bindings in that child component are still dirty-checked. – Mark Rajcok Jun 08 '16 at 17:35
  • 1
    @Fjut, the way Angular dirty-checks `{{...}}` array bindings changed in beta.16, see http://stackoverflow.com/questions/37301141/myarray-now-updates-in-the-view-as-of-beta-16. CD used to reference check the array, but now I assume it works like @Günter said... it dirty-checks the string result of the interpolation. I think the new way is more intuitive. – Mark Rajcok Jun 08 '16 at 18:09