arraysangularngfor

Why is Angular recreating a child component instead of updating it when I set an array element in the parent?


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?


Solution

  • 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;
    };