Code, Angular, iOS and more by Aviv Ben-Yosef

Improving ng-repeat Performance with “track by”

| Comments

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:

<ul class="tasks">
    <li ng-repeat="task in tasks" ng-class="{done: task.done}">
        {{}}: {{task.title}}

The problem

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:

$scope.tasks = newTasksFromTheServer;

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" 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.

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!