DEV Community

Cover image for Angular: Is trackBy necessary with ngFor?
bob.ts
bob.ts

Posted on

Angular: Is trackBy necessary with ngFor?

In a recent presentation, I had an individual very focused on the need for trackBy on my ngFor ... to the exclusion of everything else in the talk. He even went as far as to push a pull-request on my talk repository to add the change.

I was perplexed.

  1. I know there used to be a need for trackBy.
  2. Experience has shown me little need for it's use recently.
  3. Some quick research said that it is only needed if there are issues with performance with modern Angular.

So, I decided to test this out myself.

The First Iteration

In my first iteration of testing, I tested loading data to see if there was a difference in load times when displaying them with and without the trackBy. Here are the first set of tests I ran that showed some hopeful results.

Data

Basically, the data was 100,000 records with:

  • An integer, 0-based index.
  • An identifier that is a random 50 characters A-Z, a-z, or 0-9.

Summary of First Iteration

From this point, I refreshed the page 25-times each, noting the time to finish on the Network tab.

Type WITH WITHOUT
data 01 6.17 6.77
data 02 6.14 6.29
data 03 6.31 6.28
data 04 6.34 6.33
data 05 6.23 6.06
data 06 6.14 6.31
data 07 6.14 6.21
data 08 6.46 6.22
data 09 6.29 6.09
data 10 6.38 6.37
data 11 6.22 6.22
data 12 6.38 6.43
data 13 6.23 6.19
data 14 6.22 6.15
data 15 6.38 6.33
data 16 6.16 6.45
data 17 6.32 6.19
data 18 6.21 6.18
data 19 6.25 6.36
data 20 6.16 6.17
data 21 6.46 6.03
data 22 6.22 6.12
data 23 6.30 6.44
data 24 6.23 6.67
data 25 6.20 5.98
RESULTS 6.26 6.27

Conclusion

The results were one-hundredth of a second different, my conclusion might be that trackBy would only be needed if there were some significant performance need.

This path only tested Angular's ability to place data on-screen. I realized that the trackBy was built as a means to allow for faster screen updates when the data changed.

So, on to the second iteration ...

Second Iteration

I decided to go with a smaller data-set and initialize a series of changes where I swapped two indexes. I opted for a 500ms delay in between loading the data and initiating the swaps.

The (Real) Work

The repository is HERE.

Basically, the data is now 10,000 records with:

  • An integer, 0-based index.
  • An identifier that is a random 50 characters A-Z, a-z, or 0-9.

Each page connects to the service, gets the data and displays the identifier 10,000 times. On one page, we are using trackBy and in the other, we are not using it.

Additionally, after 500ms another process triggers that randomly picks two indexes and swaps them. I did this 1,000 times per page.

data.service.ts

The core to the service is as follows ...

iterations: number = 1000;
dataPoints: number = 100000;
data: Array<any> = [];
startTime: Date;

constructor() {
  this.startTime = new Date();
  this.init();
}

init = (): void => {
  for (let i = 0, len = this.dataPoints; i < len; i++) {
    const datum: any = { index: i, identifier: this.makeid() };
    this.data.push(datum);
  }
};

makeid = (length: number = 50): string => {
  var result: string = '';
  var characters: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  var charactersLength = characters.length;

  for ( var i = 0; i < length; i++ ) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }

  return result;
};

getRandomInt = (min: number, max: number) => {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
};
Enter fullscreen mode Exit fullscreen mode

with.component.ts

The components are nearly identical ...

data: Array<any> = [];

constructor(
  private dataService: DataService
) { }

ngOnInit(): void {
  this.data = this.dataService.data;
  setTimeout(this.initiateReorganize.bind(this), 500);
}

ngAfterViewChecked(): void {
  const now: Date = new Date();
  const difference: number = now.getTime() - this.dataService.startTime.getTime();
  console.log('difference: ', difference);
}

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

initiateReorganize = (): void => {
  const min: number = 0;
  const max: number = this.dataService.iterations - 1;
  for (let i = 0, len = this.dataService.iterations; i < len; i++) {
    const a: number = this.dataService.getRandomInt(min, max);
    const b: number = this.dataService.getRandomInt(min, max);
    [this.data[a], this.data[b]] = [this.data[b], this.data[a]];
  }
};
Enter fullscreen mode Exit fullscreen mode

... except, the without.component.ts does not contain an identify function.

with.component.html

And, the HTML is nearly identical, as well ...

<div *ngFor="let item of data; trackBy: identify">
  {{ item.identifier }}
</div>
Enter fullscreen mode Exit fullscreen mode

... removing the trackBy: identify in the without.component.html, as well.

Results

From this point, I refreshed the page 25-times each, noting the time to finish in the console.

Type WITH WITHOUT
Data 01 1700 1654
Data 02 1647 1669
Data 03 1634 1695
Data 04 1639 1652
Data 05 1753 1641
Data 06 1624 1693
Data 07 1627 1632
Data 08 1676 1637
Data 09 1638 1707
Data 10 1631 1630
Data 11 1625 1652
Data 12 1727 1648
Data 13 1633 1768
Data 14 1636 1641
Data 15 1684 1712
Data 16 1634 1667
Data 17 1690 1633
Data 18 1631 1730
Data 19 1624 1631
Data 20 1741 1640
Data 21 1635 1675
Data 22 1631 1641
Data 23 1690 1663
Data 24 1625 1652
Data 25 1688 1651
Results 1658.52 1664.56

Conclusion

So, given that the results are 6.04 milliseconds different (remember, less than a second difference on load of a data-set 10-times larger), my conclusion would be that trackBy would only be needed if there were some significant performance need that cropped up.

In general, it is not needed in modern Angular.

Discussion (1)

Collapse
fyodorio profile image
Fyodor

That actually makes total sense and one shouldn't just use trackBy religiously (as any other recommended "best practice"), but rather consider each iterable use case separately.

There are use cases though where absence of trackBy can cause huge performance drops. For instance when one iterates over component instances that have some costly calculations inside (triggered in ngOnInit, for instance). If these calculations don't need to be triggered if nothing changes for a specific item, trackBy will save a ton of potentially lost milliseconds.

So salt is the key, as always - you should always take everything with a grain of it.