167

I can't really understand what I should return from trackBy. Based on some examples I've seen on the web, I should return the value of some property on the object. Is it right? Why should I get index as a parameter?

For example, in the following case:

Component.component.ts

constructor() {
    window.setInterval(() => this.users = [
            { name: 'user1', score: Math.random() },
            { name: 'user2', score: Math.random() }
        ],
        1000);
}

userByName(index, user) {
    return user.name;
}

Component.template.html

<div *ngFor="let user of users; trackBy:userByName">
  {{user.name}} -> {{user.score}}
</div>

The objects shown in this template are still updated despite the name being unchanged. Why?

ng-hobby
  • 2,077
  • 2
  • 13
  • 26
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488

8 Answers8

206

On each ngDoCheck triggered for the ngForOf directive, Angular checks what objects have changed. It uses differs for this process and each differ uses the trackBy function to compare the current object with the new one. The default trackBy function tracks items by identity:

const identify = (index: number, item: any) => item;

It receives the current item and should return some value. Then the value returned by the function is compared against the value this function returned the last time. If the value changes, the differ reports a change. So if the default function returns object references, it will not match the current item if the object reference has changed. So you can provide your custom trackBy function that will return something else. For example, some key value of the object. If this key value matches the previous one, then Angular will not detect the change.

The syntax ...trackBy:userByName is no longer supported. You must now provide a function reference. Here is the basic example:

setInterval( () => {
  this.list.length = 0;
  this.list.push({name: 'Gustavo'});
  this.list.push({name: 'Costa'});
}, 2000);

@Component({
  selector: 'my-app',
  template: `
   <li *ngFor="let item of list; trackBy:identify">{{item.name}}</li>
  `
})
export class App {
  list:[];

  identify(index, item){
     return item.name; 
  }

Although the object reference changes, the DOM is not updated. Here is the plunker. If you're curious how ngFor works under the hood, read this answer.

rofrol
  • 14,438
  • 7
  • 79
  • 77
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • Can I use same `trackByFn` for 2 loops? – Yashwardhan Pauranik Oct 31 '18 at 13:11
  • 7
    @YashwardhanPauranik, if the function is pure, i.e. simply returns the result based on inputs, I don't see why you can't – Max Koretskyi Nov 01 '18 at 06:39
  • I cannot acess this (compoennt iteself) in trackbyfn . any way to do that? – Sunil Garg Apr 19 '19 at 06:13
  • 1
    @MaximKoretskyi And the answer to your question "The objects shown in template are still updated despite the name being unchanged. Why?" actually has nothing to do with `trackBy`, but rather with Angular change detection, right? I mean, **the `
    ` itself won't get repainted** (removed and then added again), but **the content of the `
    ` will get updated** anyway because in the template you bind `user.score` that changes every interval tick.
    – Glenn Mohammad Oct 31 '19 at 22:10
  • 1
    @GlennMohammad 's statement makes sense. TrackBy only answers the question "whether to delete and recreate the DOM element? ". The {{content}} inside template tag will be rendered through for loop **regardless of the value returned by trackBy(**as long as DOM exists). ----- Mostly efficiency prevails when trackBy tells angular to render the changes without destroying the DOM. ----- AFAIU comparison include index too; like " ( index, tackBy(index, item) ) ". ----- ie: what was the return value by trackBy for current index lastTime. – Vaisakh Rajagopal Mar 09 '21 at 19:51
  • Compare "trackBy returns a constant value" vs "trackBy returns an ID from Object item". trackByConstant ideally say don't destroy & recreate the DOM even if data list is re-initialize. trackByID say only do if ID changes. But if we swap the data, btw distant index ie;3 & 6. You will see DOM destroy & recreation for both, may be bcz it take index too. trackByConstant work as normal swap the DOM in position. But trackByID seems to touch all intermediate DOM (3,4,5,6). The one above 3 and Below 6 remain untouched. Its just an observation, its helpful if someone could clarify this. – Vaisakh Rajagopal Mar 09 '21 at 20:15
  • https://stackblitz.com/edit/angular-ivy-jd9jtf?file=src/app/my-list/my-list.component.ts It may not be destroyed but if you inspect the
  • elements under trackByConstant and trackBYID, you will see the glowing effect for DOM(3,4,5,6) indeces. – Vaisakh Rajagopal Mar 09 '21 at 20:27