4

I'm learning about Angular change detection process and I'm checking the Chrome dev tools, I see strange behavior.

My plnkr to demonstrate the behavior: http://plnkr.co/edit/cTLF00nQdhVmkHYc8IOu

I have a simple component view:

<li *ngFor="let item of list">{{item.name}}</li>

and the its constructor:

constructor() {
    this.list = [{name: 'Gustavo'}, {name: 'Costa'}]

to simulate a simple request I've added:

// simulating request repaint the DOM
setInterval( () => {
  this.list = [{name: 'Gustavo'}, {name: 'Costa'}];
}, 2000);

If you noticed, the array list receives a list equal to the initial value. Let's imagine when Angular checks the values in view in change detection process we have a code like this:

if( oldName !== name )  { // ( 'Gustavo' !== 'Gustavo')
 // update the view
}

But the values are the same, why angular REPAINT THE DOM every 2 seconds.?

But if I mutate the object, the REPAINT does not occur

// simulating request there is not repaint
setInterval( () => {
  this.list[0].name = "Gustavo"; // no repaint because it's the same value
  this.list[1].name = "Costa 2"; // repaint
}, 2000);

You can test this with the plnkr link above.

3 Answers3

6

This is because Angular uses default trackByFunction for the DefaultIterableDiffer that tracks items by identity.

const trackByIdentity = (index: number, item: any) => item;

So obviously when you create a new array it creates new object references and Angular detects changes. Even if you didn't change array reference, Angular will still think that items are changed because object references change:

setInterval( () => {
  this.list.length = 0;
  this.list.push({name: 'Gustavo'});
  this.list.push({name: 'Costa'});
}, 2000);

You can provide you custom trackByFunction to track by object name:

@Component({
  selector: 'my-app',
  template: `
   <li *ngFor="let item of list; trackBy:identify">{{item.name}}</li>
  `
})
export class App {
  list:[];

  identify(index, item){
     return item.name; 
  }

In this way the DOM will not be updated. See this plunker.

Since you are curios about ngFor you can also read this answer where I explain how ngFor works under the hood.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • Hey Maximus, thank you again! for AngularJS developers the '$$hashKey' is the reference of the object in Angular? trackBy for Angular has the same idea of AngularJS? (the same concepts apply in Angular with this post? http://www.codelord.net/2014/04/15/improving-ng-repeat-performance-with-track-by/)? – Gustavo Costa Jul 25 '17 at 20:07
  • @GustavoCosta, you're welcome. Yes, it's the same idea in AngularJS – Max Koretskyi Jul 26 '17 at 04:28
  • @Maximus the identify method strangely if return 1, false or nothing ( all item receive de same trackBy:id ) the repaint not occur – Gustavo Costa Jul 26 '17 at 13:31
  • @GustavoCosta, because your return the same value everytime – Max Koretskyi Jul 26 '17 at 13:41
  • Yep, but in AngularJS you can't return the same trackBy value to all elements, and it makes sense because AngularJS uses this values to track model and element in ng-repeat. – Gustavo Costa Jul 26 '17 at 13:52
  • @GustavoCosta, sorry, not sure what you question is :) Can you maybe ask another question with more details? – Max Koretskyi Jul 26 '17 at 14:10
  • First question: See: embed.plnkr.co/vIZP3U can you try repeat id:1 for all values? With this you can see the AngularJS error: Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. But this behavior not occurs in Angular, in identify method if you return 1 to all items, works fine. – Gustavo Costa Jul 26 '17 at 16:45
  • Second question: I really don't understand why Angular not 'trackBy' under the hood, because the repaint not occurs in simple binding {{user.name}} without ngFor, Angular can track the {{user}} reference and name property, if that values changes Angular knows where that ref is binding in the DOM. see: http://plnkr.co/edit/cTLF00nQdhVmkHYc8IOu And why this not occurs with *ngFor and Angular needs remove and add list again? – Gustavo Costa Jul 26 '17 at 16:48
  • Because for me, the syntax `let user of list` it's enough for the Angular to know which reference belongs to which item of the iteration and update the specific piece of the DOM. In the iteration when Angular constructs the element inside of ngFor It knows which reference is that piece of the DOM. Unfortunately, the documentation there is nothing about that detail, only that Angular remove all items from the DOM when values changed :( Thank you. – Gustavo Costa Jul 26 '17 at 16:49
  • @GustavoCosta, sorry, can't help you with AngularJS. Also, unfortunately, I don't know why the `ngFor` is implemented the way it is. Try asking a general question here – Max Koretskyi Jul 26 '17 at 18:18
1

This is because you are creating a new array every time and angular is updating because the reference has changed. If you assign it to the same reference every 2 seconds this will not be the case

otherList = [{name: 'Gustavo'}, {name: 'Costa'}];

constructor() {
  this.list = [{name: 'Gustavo'}, {name: 'Costa'}]
  setInterval( () => {
    this.list = this.otherList;
  }, 2000);    
}

Updated plunker

0mpurdy
  • 3,198
  • 1
  • 19
  • 28
  • if you assign it the same reference, nothing changes) of course Angular will not be updating DOM – Max Koretskyi Jul 25 '17 at 19:17
  • @Maximus I think this is what he's asking is it not? Why if the array isn't changing, does angular still do change detection - A: because it's by reference? – 0mpurdy Jul 25 '17 at 19:19
  • no, he is asking why udpating the array reference triggers DOM udpate while mutating the array does not – Max Koretskyi Jul 25 '17 at 19:20
  • @Maximus fair enough - but is what I have said not still answering his question because I explained why it's by reference - Osman has given a better answer, you should upvote him like I will :) – 0mpurdy Jul 25 '17 at 19:22
  • yeah) read my answer, it has to do with the way default `trackBy` function works – Max Koretskyi Jul 25 '17 at 19:24
1

To add to @0mpurdy, Objects (and so do Arrays) are never equal even if they have the same properties and values, unless one is a reference of another or if they both share the same reference.

Primitives, on the other hand, can be equal to other Primitives cause they're compared by value, not by reference. That's why there's no change detection triggered when you overwrite the values manually for the same value, but if you replace the whole thing, even if the objects are apparently equal, change detection is going to get triggered.

Osman Cea
  • 1,467
  • 9
  • 18