I have a huge ngFor, changing frequently with asynchronous data. TrackBy allows to only refresh the changing part, and I really feel the difference when I add it. It seems clear to me what is the benefit to have a trackBy returning a unique ID, for example. But I sometime see samples returning the current index.
public trackByFn(index, item) { return index }
In my table, I don't notice any differences if I directly return 'index' or 'item.id'. Both seems optimize the render (but I maybe didn't catch some bugged border cases).
So, can someone explain me what exactly happen when I return the index?
The trackBy used to improve the performance of the angular project. It is usually not needed only when your application running into performance issues. The angular ngFor directive may perform poorly with large applications.
Declare a variable inside *ngFor directive using let or as keyword. for instance say indexofelement or simply i . Assign the variable value to index .
The trackBy function takes the index and the current item as arguments and needs to return the unique identifier for this item. Now when you change the collection, Angular can track which items have been added or removed according to the unique identifier and create or destroy only the items that changed. That's all.
The syntax is *ngFor="let <value> of <collection>" . <value> is a variable name of your choosing, <collection> is a property on your component which holds a collection, usually an array but anything that can be iterated over in a for-of loop.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With