Based on your comment, and my own curiosity, I dug into the angular differ code.
I can break it down for you what happens in the 3 different scenarios and I suppose it's a nice to have knowledge as well:
First scenario:
No trackBy
defined: <div *ngFor="let obj of arrayOfObj">{{obj.key}}</div>
If there is no trackBy
defined, angular iterates over the array, creates the DOM elements and binds the data in the template within the [ngForOf]
. (Above code can be written as):
<ng-template ngFor let-obj [ngForOf]="arrayOfObj">
<div>{{obj.key}}</div>
</ng-template>
So basically, it creates all those div elements. Initially this is the same for all 3 possibilities. Now new data arrives from the API, more or less the same data, but the reference to the array object changes, and all the references from the objects in the initial array are different. If you do not define a trackBy function, angular compares by identity ===
. This will go well with strings, numbers and other primitives (not that many out there). But for objects, this will not. So what happens now if it starts checking if there are changes. It cannot find the original objects anymore so it removes the DOM elements (actually stores it somewhere for later use, if an object decides to come back), and build all the templates from scratch.
Even if the data hasn't changed, the second response produces objects with different identities, and Angular must tear down the entire DOM and rebuild it (as if all old elements were deleted and all new elements inserted).
You can imagine that this can be quite cpu hungry, and memory hungry.
Second scenario:
trackBy
defined with object key:
<div *ngFor="let obj of arrayOfObj;trackBy:trackByKey">{{obj.key}}</div>
trackByKey = (index: number, obj: object): string => {
return object.key;
};
Let's do this one first. It's the quickest one. So we are at the point where new data comes in, with objects of different identities than before. At this point, angular iterates all new objects over this trackBy function and get the identity of the object. It will than cross reference it to existing (and previously deleted if not found) DOM elements. If found, it will still update any bindings made inside the template. If not found, it will check previously removed objects, and if it still cannot find it, it will create a new DOM element from the template and update the bindings. So, this is quick. Just looking for already created DOM elements, and update bindings, which angular can do quick quick quick.
Third scenario:
trackBy
defined with array index
<div *ngFor="let obj of arrayOfObj;trackBy:trackByIndex">{{obj.key}}</div>
trackByIndex = (index: number): number => {
return index;
};
This is the same story as the trackBy object key, but with the small difference that if you go play juggle with the elements inside the array, the bindings within the templates keep getting updated. But this is still fast, but most likely not the fastest way :), although it's a lot faster than recreating the entire DOM.
Hope you get the difference now. A little something extra though. If you have a lot of business objects which all have the same way to access their identity, like a property .id
or .key
, you can extend the native *ngFor
and create your own structural directive which has this trackBy
function built in. Untested code though:
export interface BaseBo {
key: string;
}
@Directive({selector: '[boFor][boForOf]'})
export class ForOfDirective<T extends BaseBo> extends NgForOf<T> {
@Input()
set boForOf(boForOf: T[]) {
this.ngForOf = boForOf;
}
ngForTrackBy = (index: number, obj: T) => obj.key;
}