Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular trackBy returning the index in *ngFor vs item id

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?

like image 426
Yoann Augen Avatar asked Feb 01 '19 11:02

Yoann Augen


People also ask

What is the difference between trackBy and ngFor?

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.

Which is the correct statement to fetch the index value in * ngFor?

Declare a variable inside *ngFor directive using let or as keyword. for instance say indexofelement or simply i . Assign the variable value to index .

How does ngFor trackBy work?

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.

Which of the following is the correct syntax for getting index using * ngFor?

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.


1 Answers

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;
}
like image 87
Poul Kruijt Avatar answered Nov 15 '22 19:11

Poul Kruijt