use *ngFor trackBy
Published on November 17, 2023
Understanding the *ngFor
trackBy
function is essential to improve the performance of your *ngFor
loops and your angular application as a whole.
What is *ngFor
trackBy?
*ngFor
trackBy is a function that you can pass to your *ngFor
loop to tell angular how to track the items in your list. By default, angular tracks the items in your list by their identity i.e ===
.
Default trackBy
Let’s understand how the default trackBy
is working.
Start a new Angular application:
change your app.component.ts
to look like this:
In this example we have a list of todo items. We are using *ngFor
to loop over the items and render an input for each item. We also have a button that adds a new item to the list.
We are placing an input
in each item to verify if the list elements are recreated from scratch or is the list modified and an element is just appended to the list.
You can type something in an input, if the list is recreated you will lose the state of the dom element (since it’s destroyed), but in this case we see that angular rocks this *ngFor
keeping the dom elements intact and just appending an element to the end of the list.
Angular will iterate on each list item and compare it to the previous list item using ===
since the objects in the list are the same, so are the elements in the *ngFor
.
The problem
As long as the objects in the array stay the same we have no problem, the problem will start when the objects in the array will change, which often happens in the common use case of *ngFor
where we iterate on a list of objects that are fetched from the server.
Let’s imagine the following use case, the list is grabbed from the server, and we have a search input that will send a search request to the server and provides us with a new list.
In that case, the objects in the list will change, and angular will destroy the dom elements and recreate them from scratch.
We can try and simplify this example with the following code:
Notice that on every add we are completly recreating the list, we are using the clone
function to clone the list and add a new item to the list.
In the following case if you type something in the input and click the add button you will see that the input is recreated from scratch and you lose the state of the input.
Regardless of losing the state, it is also a performance issue, since angular will destroy all the dom elements in the list and recreate them from scratch.
The solution
The solution is to use *ngFor
trackBy function, which will replace the default ===
comparison with a custom comparison function.
Try and type a text in the input and click the add, you will see that the state of the input is preserved which means that the dom element is not recreated from scratch.
Lint rule
Since it’s so common that *ngFor
will get a new list, there are places that enforce the use of *ngFor
trackBy function, by enabling a lint rule in @angular-eslint
.
You can update the .eslintrc
file:
notice that we added the "parser": "@angular-eslint/template-parser",
and the rule: "@angular-eslint/template/use-track-by-function": "error"
.
Now when you run npx ng lint
without the trackBy
function you will get a lint error.
I do recommend adding this lint although it’s not a must if the angular default comparison ===
is suitable for most of your use cases, and your lists are not recreated with the default comparison.
Conclusion
When placing *ngFor
in your Angular application, ask yourself the following:
- Which elements in the list are recreated, and how do I optimize it so only the needed elements of the list are actually updated, and not the whole list.