Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
933 views
in Technique[技术] by (71.8m points)

typescript - 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...