Why use trackyBy with ngFor directive
- ngFor directive may perform poorly with large lists.
- A small change to the list, like adding a new item or removing an existing item, may trigger several DOM manipulations.
Let’s look at an example. Suppose, we are having an “Employee Component” in the project.
Now, consider this below code in the employee.component.ts file.
Note
- The constructor() initializes the “employees” property with 4 employee objects.
- getEmployees() method returns another list of 5 employee objects (The 4 existing employees + a new employee object).
- import { Component, OnInit } from '@angular/core';
-
- @Component({
- selector: 'app-employee',
- templateUrl: './employee.component.html',
- styleUrls: ['./employee.component.css']
- })
- export class EmployeeComponent implements OnInit {
- employees: any[];
- ngOnInit(){}
- constructor() {
- this.employees = [
- {
- code: '1001', name: 'drashti', gender: 'Female',
- salary: 55500
- },
- {
- code: '1002', name: 'namrata', gender: 'Female',
- salary: 57000
- },
- {
- code: '1003', name: 'shreeja', gender: 'Female',
- salary: 59000
- },
- {
- code: '1004', name: 'shreenil', gender: 'Male',
- salary: 65000
- }
- ];
- }
-
- getEmployees(): void {
- this.employees = [
- {
- code: '1001', name: 'drashti', gender: 'Female',
- salary: 55500
- },
- {
- code: '1002', name: 'namrata', gender: 'Female',
- salary: 57000
- },
- {
- code: '1003', name: 'shreeja', gender: 'Female',
- salary: 59000
- },
- {
- code: '1004', name: 'shreenil', gender: 'Male',
- salary: 65000
- },
- {
- code: '1005', name: 'tejas', gender: 'Male',
- salary: 67000
- }
- ];
- }
- }
Now, look at this code in employeeList.component.html.
- <div class="container">
- <table class="table">
- <thead>
- <tr>
- <th>Code</th>
- <th>Name</th>
- <th>Gender</th>
- <th>Annual Salary</th>
-
- </tr>
- </thead>
- <tbody>
- <!-- -->
- <tr *ngFor='let employee of employees'>
- <td>{{employee.code}}</td>
- <td>{{employee.name}}</td>
- <td>{{employee.gender}}</td>
- <td>{{employee.salary}}</td>
-
- </tr>
- <tr *ngIf="!employees || employees.length==0">
- <td colspan="5">
- No employees to display
- </td>
- </tr>
- </tbody>
- </table>
- <br />
- <button class="btn btn-primary" (click)='getEmployees()'>Refresh Employees</button>
- </div>
Ok now, let’s check what we are exactly doing. At the moment, we are not using trackBy with ngFor directive. So what the above code will do is that:
- When the page initially loads, we see 4 employees.
- When we click the "Refresh Employees" button, we see the fifth employee also.
- It will look like it has just added the additional row for the fifth employee. But that's not true, it effectively destroyed all the <tr> and <td> elements of all the employees and recreated them.
- To confirm this, launch your browser with developer tools by pressing F12.
- Then, click on the "Elements" tab and expand the <table> and then <tbody> elements.
- At this point, again, click the "Refresh Employees" button and you will notice all the <tr>elements are briefly highlighted. This indicates that they are destroyed and recreated.
Here is the initial page load screen with 4 employee objects.
When we click on the “Refresh Employees” button.
In the above screen, you can check that when the button is clicked, it will add a fifth object of Employee but along with that, it will destroy the previously added objects and recreate them. You can see in your browser that they all are highlighted.
This happened because, by default, Angular keeps track of these Employee objects using their object references and when we click the button, we are returning new object references (by calling getEmployees() method). So, Angular does not know whether it is old objects collection or not and that’s why it destroys the old list and recreates a new one.
This can cause a problem when we are dealing with a large number of objects or list. The performance issues will arise. So, to avoid this, we can use trackBy with ngFor directive.
Now, we will add a trackBy function in the employee.component.ts file.
The trackBy function takes the index and the current item as arguments and returns the unique identifier by which that item should be tracked. In our case, we are tracking by Employee code.
- trackByEmpCode(index: number, employee: any): string {
- return employee.code;
- }
And also, make the following changes in employeeList.component.html.
Notice that along with ngFor, we also specified trackBy.
- <tr *ngFor='let employee of employees; trackBy:trackByEmpCode'>
Now, run the application.
At this point, run the application and check the developer tools. When you click "Refresh Employees" the first time, only the row of the fifth employee is highlighted indicating only that <tr> element is added. When you click further, nothing is highlighted. It means that none of the <tr> elements are destroyed or added as the Employees collection has not changed. Even now, we get different object references when we click the "Refresh Employees" button, but as Angular is now tracking employee objects using the employee code instead of object references, the respective DOM elements are not affected.