Mystery behind track by in ng-repeat

This is how AngularJS works under the covers (in a nutshell) when you use ng-repeat:

  1. It iterates over each item in the array (or each key, value in the object)
  2. It runs each item via any filters that are present in our expression, to check if it should be displayed or not
  3. It calculates a hash value by which it identifies the object (which is by reference by default)
  4. It checks if it has already created a DOM element for the hash value previously
    If so, it reuses it
  5. If not, it creates a DOM element based on the ng-repeat template
  6. All the DOM manipulations are taken and inserted in an optimal manner into the actual DOM
  7. A watch is added on the array, which triggers step 1 again if the array undergoes any change

$$hashkey is a auto generated key which angular assigns to array,collection passed in ng-repeat directive. Angular uses it to keep track of any changes made to array and update doom accordingly.

As we know ng-repeat is used to tight up collection to DOM. Suppose we have an collection of 20 elements and are using it create a complex DOM structure. Suppose we make changes to the array and add or edit elements, this would result in angularjs recreating entire dom structure for the new array. This can become a costly operation in many cases, causing lot of browser repaints. By passing “track by” to ng-repeat which is unique to the array, when array changes, angular only recreates dom only for the changed array value, and not the entire array. “track by” tells AngularJS that each item is uniquely identified by the property passed in the track by expression, and to reuse DOM elements as long as the that field does not change. This is a quick and simple step to optimizing ng-repeat in AngularJS, to ensure DOM manipulations in your application are reduced.

Consider following example

TrackBy-1

angular.module('myApp', [])
.controller('tasksCtrl', function($scope) {
	 
    function getTasks(){
    	var tasks = [];
    	for (var i = 1; i < 301; i++) {
         var task = {
            id: i,
            title: 'Task ' + i
         }
         task.latestDate = new Date().toISOString();
         tasks.push(task);
    	}
      return tasks;
    }
    $scope.tasks = getTasks();
       
    $scope.refresh = function() {
        console.log("refreshing")
        $scope.tasks = getTasks();
    };
})

 

As you can see when we click refresh we are assigning new tasks collection. It would cause ngRepeat to remove all li elements of existing tasks and create them again, which might be expensive (e.g. we have a lot of them or each li’s template is complex). That means a lot of DOM operations.

Behind the scenes ngRepeat adds a $$hashKey property to each task to keep track of it. If you replace the original tasks with new tasks objects from the server, even if those are in fact totally identical to your original tasks, they won’t have the $$hashKey property and so ngRepeat won’t know they represent the same elements.

TrackBy-2

You can change the above to be ng-repeat="task in tasks track by task.id" and since the ID would be the same in both your original tasks and the updated ones from the server – ngRepeat will know not to recreate the DOM elements and reuse them.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s