I have a parent component
@Component({
selector: 'parent-component',
templateUrl: 'parent-component.html',
})
export class ParentComponent {
items : SomeClass[] = [....]; // Populated array
onSomeEvent() {
this.items[1] = new SomeClass(..);
}
}
With template
<tr *ngFor="let item of items">
<child-component [item]="item"/>
</tr>
I have a child component
@Component({
selector: 'child-component',
templateUrl: 'child-component.html',
})
export class ChildComponent implements OnChanges {
@Input() item! : SomeClass;
constructor() {
console.log('Child#constructor');
}
ngOnChanges(changes : SimpleChanges) : void {
for (const propName in changes) {
const change : SimpleChange = changes[propName];
console.log('Child#ngOnChanges prop="' + propName + '" change=' + change);
}
}
}
I change one of the elements of the array in the parent and the child component is recreated instead of being notified of a change in input.
Ie Calling ParentCompoenent#onSomeEvent() causes log of:
Child#constructor
Child#ngOnChanges prop="item" change={previousValue:undefined currentValue=SomeClass(..) firstChange=true}
Why is ChildComponent being recreated instead of just having ngOnChanges called on it?
When we do not set trackBy
function on an *ngFor
, ngFor has no idea which value has changed, since each element in the array is stored as references (locations in memory)
From the perspective of ngFor, its impossible to know which element was changed, so it rerenders!
So with the use of trackBy, we can inform *ngFor
that a primitive can be used to identify which element is which, good values will be some id field, or using index
Code Change needed!
html
<tr *ngFor="let item of items;trackBy:trackByIndex">
<child-component [item]="item"/>
</tr>
ts
trackBy = (i: number): number => {
return i;
};
Example of track by value
trackByValue = (index: number, item: object): string => {
return item.id;
};