0

I have an issue with change detection running too often within one of my components, so I'm trying to use the trackBy option for the ngFor directive.

Through reading, I understand that Angular will use the value returned from your trackyBy function for it's diff the next time change detection runs. To see if it fits my needs, and to try and understand it better, I set up a playground. When using it, I set the return value of the trackyBy2020欧洲杯时间表 function I use to return undefined, and I still got the results I wanted.

TS:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  styleUrls: ['./app.component.scss'],
  templateUrl: './app.component.html',
})
export class AppComponent {
collection;
  constructor() {
    this.collection = [{id: 1, value: 0}, {id: 2, value: 0}, {id: 3, value: 0}];
  }

  getItems() {
    this.collection = this.getItemsFromServer();
  }

  getItemsFromServer() {
    return [{id: 5, value: 0}, {id: 2, value: 0}, {id: 3, value: 3}, {id: 4, value: 4}];
  }

  trackByFn(index, item) {
    return undefined;
  }
}

HTML:

    <ul>
      <li *ngFor="let item of collection;trackBy: trackByFn">{{ item.id }}hello {{item.value}}</li>
    </ul>
    <button (click)="getItems()">Refresh items</button>

The results on the first click is, all items rerender with their new value or id, except index 1 of the array. On second click, none of the items rerender bc nothing changes within the objects.

So my question is, why would one ever use a unique id for the return value of trackBy function? There has to be something I am missing and I'm not wanting it to affect my application in a way I don't see yet.

  • Consider using OnPush change detection strategy. It has greater performance impact other than trackBy – Sergey 10 hours ago
1

The official answer is that you use trackBy to avoid recreating elements in the DOM for new instances of objects that have the same identifier.

On the face of it, your setup doesn't prove that ngFor isn't just ignoring the undefined value you are returning in trackByFn and recreating the DOM elements anyway when new instances appear in the model.

New items with the same id as existing objects may have had other properties changed, so you would expect the HTML to be correct regardless of whether or not you use (or mis-use) trackBy.

The setup

I created a test environment using your code, except I forked the source code for *ngFor so that I could add my own logging to trace what happens inside *ngFor.

I tested three scenarios:

A) trackByFn returns the unique id

B) trackByFn returns undefined

C) does't use trackBy

I traced what happens in each scenario for the following steps

  1. create list
  2. partially replace some list data
  3. sort list
  4. reset list data

2020欧洲杯时间表I assigned new instances of objects at each step for a "pure" test.

The results

1. create list

The same for all 3 scenarios - a DOM element is created for each item in the list.

2. partially replace some list data

2020欧洲杯时间表A) removes DOM elements for removed items and creates new DOM elements for new items. All elements are updated.

2020欧洲杯时间表B) creates new DOM elements for items with an index out of the bounds of the original array. All elements are updated.

2020欧洲杯时间表C) recreates all DOM elements.

3. sort list

2020欧洲杯时间表A) moves the DOM elements that have moved positions

B) updates the DOM elements that have moved positions

C) recreates all DOM elements

4. reset list data

2020欧洲杯时间表A) removes DOM elements for removed items and creates new DOM elements for new items. All elements are updated (same as scenario 2).

B) removes DOM elements for removed items. All elements are updated.

C) recreates all DOM elements.

Conclusions

It is important to note that these tests were done using new instances of objects. *ngFor is more efficient if you are reusing object references.

Using trackBy2020欧洲杯时间表 is more efficient in terms of DOM manipulation if you have a very volatile list.

The surprising result

From my tests it appears that your example does less DOM manipulation than when returning the unique identifier from trackByFn2020欧洲杯时间表. If you replace 3 items with 3 new items, your method wouldn't do any DOM manipulation and would still run the same update method as the "proper" way. The "proper" method would remove the original 3 DOM elements and add 3 new DOM elements.

This suggests that we could just provide a trackByFn that returns a constant value without any unexpected results. From having looked through the source code and played around with it, I can't see how this is a problem (aside from confusing other people who look at your code).

This does make me wonder why the default implementation has to recreate all of the DOM elements, when reusing old DOM elements seems to work just fine. I'm sure there are some cases that I've not considered, and I would love to hear them.

DEMO:

This turned into a bit of "fun" research task rather than a definitive answer, but hopefully it proves useful. Even though I've shown that returning a constant value from trackByFn2020欧洲杯时间表 seems to be the most performant option, I'd still be hestitant about using this approach in production code. Even if it works for all cases now I wouldn't be surprised if it were to be "fixed" at some point as a bug.

ngForOf source code:

|improve this answer|||||
0

TLDR; don't return undefined2020欧洲杯时间表 or some other constant in your trackBy. It doesn't mean anything and there are better alternatives whatever is your need.

The implementation details of ngFor are quite complex and I'm not going to dissect them for the sake of this answer. Besides, we really don't need to analyze every single detail. We just need to understand what's the purpose of trackBy:

trackBy is a mean to ease the tracking of items in the iterable source, in order to (1) optimize performance and (2) prevent destruction of items which could be kept alive.

(1) is obvious: the less DOM manipulation, the better performance is. (2) not so much: if I want to animate elements in the list (e.g. move elements while animating a sort operation, or showing an :enter or :leave2020欧洲杯时间表 animation) I really need that the instantiated components are "properly" tracked. If I don't setup trackBy correctly, the items will be needlessly destroyed and recreated, instead of moved around correctly.

2020欧洲杯时间表To better show this, I did setup an example to show the point:

Each ngFor generates AppCounter instances, capable of tracking the cumulative number of constructor calls and displaying a name value. In other words, we can track if the component is recreated and if the component input is reset.

The first button changes the value of the name binding. No ngFor2020欧洲杯时间表 recreates the components, because the items are by default tracked by object instance.

The second button (Create New Instances) shows that even if we change completely the instances, rebuilding the objects in the array from the ground up, we can tell the ngFor component to manage the component creation in a smarter way. That is: the Counters for complex values - no trackBy recreates all the components at every click on the button; so does the trackByUndefined, while the trackByProperty correctly prevents component destruction. This is crucial if you need to animate enter and leave, or if component creation can be costly. This is particularly true for item components with onPush changeDetectionStrategy.

The trackByProperty method is a one liner:

trackByPropertyName(idx: number, value: any) {
  // use ?. on new TypeScript versions
  return value && value.name;
}

The second button also randomizes the array randomValues. This is used to show another alternative to the trackByProperty method: trackByIndex.

If you really want to keep all the components in the same order they are rendered, and you want only the data to be updated, one simple but effective option is to use:

trackByIndex(idx: number, _value: any) {
  return idx;
}

2020欧洲杯时间表This obviously prevents animations when sorting, but it keeps the instantiated components "alive", even if the value change completely.

This answer is already pretty long, almost a blog post, but I want to reiterate the point: use a trackBy to tell ngFor what strategy to use to optimize component instantiation. The function is going to be tipically a one liner, just returning an id property or the index of the row, so there is no added complexity in doing so, but it will make the performance and the behavior of your ngFors better and a lot more predictable. Use the default only if you have simple values or you don't care about performance at all (but, really, who doesn't?).

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.