Angular Trackby option improves the Performance of the ngFor if the collection has a large no of items and keeps changing. Learn why we need it and how to use it to improve the performance of the ngFor
.
Table of Contents
Trackby in ngFor
We usengFor
to display a iterable items like array in a list or tabular format. For Example the following code iterates over the movies
collection and displays each movie
inside an ul
1 2 3 4 5 6 7 | <ul> <li *ngFor="let movie of movies"> {{ movie.title }} - {{movie.director}} </li> </ul> |
The Angular creates a li
element for each movie. So if there are n
number of movies, the angular inserts the n
number of li
nodes into the DOM
But the data will not remain constant. The user will add a new movie, delete a movie, sort the list in a different order, or simply refresh the movie from the back end. This will force the angular to render the template again.
The easiest way to achieve that is to remove the entire list and render the DOM again. But this is inefficient and if the list is large it is a very expensive process.
To avoid that the Angular uses the object identity to track the elements in the collection to the DOM nodes. Hence when you add an item or remove an item, the Angular will track it and update only the modified items in the DOM.
But if you refresh the entire list from the back end, it will replace the objects in the movie collection with the new objects. Even if the movies are the same, Angular will not be able to detect as the object references have changed. Hence it considers them new and renders them again after destroying the old ones.
The following example shows what happens when we refresh the entire list. The App displays the list of movies. it has option to add a movie, remove a movie and refresh the entire movie.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { title: string = "Top 5 Movies"; movies=[]; mTitle:string=""; mDirector:string=""; ngOnInit() { this.Refresh(); } remove(i) { this.movies.splice(i,1); } addMovie() { this.movies.push({ title: this.mTitle, director: this.mDirector}) this.mTitle="" this.mDirector="" } Refresh() { console.log("refresh") this.movies = [ { title: 'Zootopia', director: 'Byron Howard, Rich Moore'}, { title: 'Batman v Superman: Dawn of Justice', director: 'Zack Snyder'}, { title: 'Captain American: Civil War', director: 'Anthony Russo, Joe Russo'}, { title: 'X-Men: Apocalypse', director: 'Bryan Singer'}, { title: 'Warcraft', director: 'Duncan Jones'}, ] } } class Movie { title: string; director: string; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <h1> {{title}} </h1> <ul> <li *ngFor="let movie of movies; let i=index;trackBy: trackByFn;"> {{i}}. {{ movie.title }} - {{movie.director}} <button (click)="remove(i)">remove</button> </li> </ul> <button (click)="Refresh()">Refresh</button> <br> Title : <input type="text" [(ngModel)]="mTitle"> Director : <input type="text" [(ngModel)]="mDirector"> <button (click)="addMovie()">Add</button> |
You can see from the above example, that Angular renders the entire DOM every time we click on refresh.
Trackby
We can solve this problem by providing a function to the trackBy
option that returns a unique id for each item. The ngFor
will use the unique id returned by the trackBy
function to track the items. Hence even if we refresh the data from the back end, the unique id will remain the same and the list will not be rendered again.
The trackBy
takes a function that has two arguments: index
and the current item
. It must return a id
that uniquely identifies the item. The following example returns the title
as the unique id.
1 2 3 4 5 | trackByFn(index, item) { return item.title; } |
In the template assign the newly created trackByFn
to trackBy
option in the ngFor
statement.
1 2 3 | <li *ngFor="let movie of movies; let i=index;trackBy: trackByFn;"> |
Trackby multiple fields
You can also trackby multiple fields as shown below
1 2 3 | <li *ngFor="let movie of movies; let i=index;trackBy: trackByFnMultipleFields;"> |
1 2 3 4 5 6 | trackByFnMultipleFields(index, item) { return item.title + item.director; } |
Can you provide course for any backend technologies from your end ?
Oh, I need to use it in my app… Use a lot of ngFor there and the events are triggered at the every simple mouse move
nice explanation.