Search code examples
angularangular-directivengfor

What is the purpose of ngForTrackBy in the NgForOf directive


From the source code of Angular, the following snippet is confusing me.

 /**
   * A function that defines how to track changes for items in the iterable.
   *
   * When items are added, moved, or removed in the iterable,
   * the directive must re-render the appropriate DOM nodes.
   * To minimize churn in the DOM, only nodes that have changed
   * are re-rendered.
   *
   * By default, the change detector assumes that
   * the object instance identifies the node in the iterable.
   * When this function is supplied, the directive uses
   * the result of calling this function to identify the item node,
   * rather than the identity of the object itself.
   *
   * The function receives two inputs,
   * the iteration index and the node object ID.
   */
  @Input()
  set ngForTrackBy(fn: TrackByFunction<T>) {
    ...
      }
    }
    this._trackByFn = fn;
  }

And here in an example from the official docs where the trackBy function is passed.

trackById(index: number, hero: Hero): number { return hero.id; }

The source code explains that

When this function is supplied, the directive uses the result of calling this function to identify the item node, rather than the identity of the object itself.

But in the example above, we are passing the identity of the object itself anyway (return hero.id;), but through an additional function. Why do we need that? If we did not pass any such function, wouldn't Angular do what it does by default as per the docs, which is, getting the identity of the object itself?

What exactly does the explicit passing of a tracker function do differently than what Angular does normally without it?

Thanks.


Solution

  • Angular will check the reference in memory for a non primitive type. If you don't pass the trackById method in, every time the array changes it thinks every object is new and recreates all the DOM nodes.

    If pass it trackById, it will check the original object with the new object, and if their id properties are the same, it says they are the same object and it won't re-drawn that DOM node.

    Here's an example you can run as well, which is just plain javascript

    let obj1 = {id: 1, name: 'test'};
    let obj1a = {id: 1, name: 'test'};
    let prim1 = 1;
    let prim2 = 1;
    
    const checkById = (a, b) => {
      return a.id === b.id;
    }
    
    // obj1 and 1a are not the same, because they are different references in memory
    console.log("obj1 === obj1a", obj1 === obj1a);
    
    // ob1 and obj1a are the "same" to us though, because the ID property is the same
    console.log("checkById(obj1, obj1a)", checkById(obj1, obj1a));
    
    // primitives would be equal
    console.log("prim1 === prim2", prim1 === prim2);