This blog is part of an Angular instructional / tutorial series, please find the first edition here: Angular structural directives demystified.

Optimize your Angular app using trackBy

When rendering lists using Angulars ngFor directives, objects are compared by reference. This is fast, but can result in unwanted DOM manipulations. Luckily, you can avoid these using the trackBy feature.

Imagine a list of users:

users = [
  { id: 1, name: 'John' },
  { id: 2, name: 'Luke' },

To render each user in a <user-details> component, we can use an *ngFor directive:

<user-details *ngFor="let user of users" 

Now imagine a refresh button, which will replace the list with a new list. A realistic scenario. For example, the same happens when you fetch data using HTTP.

refresh(): void  {
  this.users = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Lucky Luke' },
    { id: 3, name: 'Peter' },

Now, when I call refresh(), the previous components are removed from the DOM, and three new components are created.

This happens, because Angular checks each object by its reference, and when an object is different, it treats it as a new object and it renders a new view container for that object.

In this scenario, however, I only want a new component to be created for the new user, and I want to update the existing components. This could be done if you tell Angular to compare each object (user) by itsid property.

I can achieve that by adding a trackBy function, like this:

<user-details *ngFor="let user of users; trackBy: trackByFn" 

trackByFn is the name of the function it refers to, so I need to implement it and tell Angular to track by the users’ ids:

trackByFn(index: number, user: User): number {

The function is called with an index as a first argument and the object as the second argument.

Now you see only one new component is created, for the third user. The benefit is that this is more efficient and your page reacts more fluently when it receives new data.

Keep in mind, that the components should support getting updated with new data. That means, that you should do most logic in your OnChanges life-cycle hook, as this hook is called when new data is bound to the Input bindings.

You can see it in action in the following video: