Probably one of the very first things you learned doing with Angular was how to use
ngRepeat. It’s so easy and simple, you gotta love it. Here’s an example:
1 2 3 4 5
Now say you have a button above that list to refresh it.
The obvious implementation for this refresh would be something along these lines in your controller:
This is a trivial piece of code, but 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. That would make us sad pandas.
In something of a more real-world use case, where instead of our simple example template each task is its own directive, this might be very costly. A task directive that calculates a view model with even relatively simple operations like formatting dates is enough to make the refresh feel laggy. There’s a link to a fiddle with an example at the bottom of the post.
Why would Angular do this? 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.
The annoying solution
The first solution that comes to mind to most developers is to not replace the whole
$scope.tasks list, but update in place all the existing tasks objects with the data you received from the server. That would work because it means
$$hashKey property would be left intact in the original objects.
But that’s not fun, is it? I hate surgically tinkering with objects, as it is error prone, less readable and a pain to maintain.
track by to the rescue
In Angular 1.2 a new addition was made to the syntax of
ngRepeat: the amazingly awesome
track by clause. It allows you to specify your own key for
ngRepeat to identify objects by, instead of just generating unique IDs.
This means that 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. Boom.
To get a better feel of the difference this makes, see this fiddle. By default it doesn’t use
track by and the date formatting it does is enough to make clicking “Refresh” feel noticeably slow on my computer. Try adding the
track by and see how it magically becomes instant, because the
task directive’s link function is no longer called on each refresh.
It’s really that easy to get a quick little boost in performance that also saves you writing annoying code.