I am ing from React and I've been working with Angular for a few months now. A React ponent updates only the necessary HTML when is re-rendered, while an Angular ponent seems to re-render pletely 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 ponent 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 ponent 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 ponent just to notice when the ponent 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?
I am ing from React and I've been working with Angular for a few months now. A React ponent updates only the necessary HTML when is re-rendered, while an Angular ponent seems to re-render pletely 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 ponent 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 ponent 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 ponent just to notice when the ponent 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?
Share Improve this question edited May 26, 2019 at 12:49 Sergiu asked May 26, 2019 at 12:43 SergiuSergiu 1,3961 gold badge18 silver badges33 bronze badges 5-
forEach
changes the array items in place.map
creates a new array; therefore a full re-render takes place. – R. Richards Commented May 26, 2019 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 Commented May 26, 2019 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 Commented May 26, 2019 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 Commented May 26, 2019 at 13:27 -
Do I need to write my own
ngFor
directive? – Sergiu Commented May 26, 2019 at 13:30
1 Answer
Reset to default 6NgForOf 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 pletely 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 pares 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