2

I am coming from React and I've been working with Angular for a few months now. A React component updates only the necessary HTML when is re-rendered, while an Angular component seems to re-render completely when the state is not mutated. If I mutate the state directly seems to work just fine.

Here's my very basic example:

I have the main component which holds the state:

items = [{ name: "John", age: 8 }, { name: "Jane", age: 20 }];

and in its view it renders a list of items:

<item *ngFor="let item of items" [item]="item"></item>

the item component looks like this:

@Component({
  selector: "item",
  template: `
    <p>
      {{ item.name }}, {{ item.age }}
      <input type="checkbox" />
    </p>
  `
})
export class ItemComponent {
  @Input() item: any;
}

I added that checkbox in the item component just to notice when the component is fully re-rendered.

So what I do is tick the checkboxes and increment the age for each user in the state.

If I increment it by mutating the state the view is updated as expected and the checkboxes remain checked:

this.items.forEach(item => {
  item.age++;
});

But if I change the age without directly mutating the state the entire list is re-rendered and the checkbox is not checked anymore:

this.items = this.items.map(item => ({
  ...item,
  age: item.age + 1
}));

Here's a full example in a CodeSandbox.

Can anyone please explain why is this happening and how can I make the list to not fully re-render when I don't mutate the state?

Sergiu
  • 1,397
  • 1
  • 18
  • 33
  • [`forEach`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) changes the array items in place. [`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) creates a new array; therefore a full re-render takes place. – R. Richards May 26 '19 at 12:57
  • @R.Richards Can I somehow make `*ngFor` to not re-render the entire list of items when I don't mutate the state? – Sergiu May 26 '19 at 13:11
  • If you’re replacing the array each time the state changes, no. Are you going to add the checkbox value to your state? Right now, they aren’t bound to anything. If you are going to want to track a selected item via the checkbox, you’re going to need that in the state, too. Not sure if that is the goal. – R. Richards May 26 '19 at 13:19
  • The goal is to not re-render every item every time I make a change in the state. If, for example, I would change the `age` property of a single item the entire list would re-render, which doesn't feel right to me. – Sergiu May 26 '19 at 13:27
  • Do I need to write my own `ngFor` directive? – Sergiu May 26 '19 at 13:30

1 Answers1

6

NgForOf directive which Angular uses to render list of items checks if there are any changes in array we pass to it.

If you mutate property in items then Angular knows that there is no changes since references to objects in array remain the same.

On the other hand, if you completely replace the previous object with new one then it is signal for ngForOf to rerender that object.

To determine if an object changes ngForOf directive use DefaultIterableDiffer that looks at trackByFn function if we provided it. If we don't provide it then the default function looks like:

var trackByIdentity = function (index, item) { 
  return item;
};

And then Angular compares result of this function with previous value in collection.

You can think about trackByFn like key in React.

So your solution might look like:

*ngFor="let item of items; trackBy: trackByFn"

trackByFn(i) {
    return i;
}

https://codesandbox.io/s/angular-67vo6

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • I understand all those things but what if I only modify a property of a single object in the list and I use something like ImmutableJS to handle the state? The whole list will re-render even if a single item in the list changed. – Sergiu May 26 '19 at 15:36
  • 1
    In your example you're changing all items in array. Here's an example where I change only Jane https://codesandbox.io/s/angular-p53pq As you can see it rerenders only Jane. Anyway I suggest you using trackBy feature. – yurzui May 26 '19 at 15:54