Angular 1.x Performance Quick Tip: ng-repeat + track by

Juan Carlos Garzon
iCapital Network Technology Group
3 min readJul 23, 2018

As software developers, performance often becomes one of those things you don’t worry about until you have too, and it’s especially true when it comes to Front End performance. We often spend more time focusing on making our API’s snappy that we overlook how well the browser is responding to all that data you’re sending back.

To shine more light on the front end, I wanted to provide a quick tip for improving the performance of your Angular 1.x Application.

Even as I write this, I’m well aware that Angular 1.x is on its way out in favor of React and the later versions of Angular. However, the tradeoff here is that you can implement these performance improvements faster than you could if you choose to rebuild your UI React of Angular 2+.

Most of your performance hits are likely to be located within an ng-repeat segment. It’s one of the first things you’re probably learned how to do in Angular and then probably haven’t thought about it much since.

Here is an example.

<div class="chores-container">
<table>
<tbody>
<tr ng-repeat="chore in chores">
<td>{{chore.assigned_to}}</td>
<td>{{chore.name}}</td>
<td>{{chore.done ? 'Done' | 'To Do'}}</td>
<td ng-show="chore.priority">{{chore.priority}}</td>
</tr>
</tbody>
</table>
</div>

The Problem

So what kind of problem can arise from such a simple example? What would happen if you had 10 chores or 100 chores or maybe you’re a parent and have 2000+ chores?

Well, consider what would happen if you had to reload the data from the server, in each of those scenarios. Also, consider how you’re loading that data.

If you’re like most people, you’re probably using something like this $scope.chores = newChores; in your controller after you’ve received your response from the server.

It’s a simple way of loading your data, and the most common, but it can have drastically different outcomes depending on how much data you’re trying to load or how complicated your template is.

Without any changes, reloading data in this way would cause angular to take all the items in the ng-repeat, by removing all existing <tr> and associated <td> elements from the DOM and recreating them again by rerunning every associated function call, resynchronize all bindings, and make a ton of expensive DOM operations.

Rebuilding like this might not be a concern if you’re only working with 10 or maybe even 100 items, but keep increasing your dataset, and you’ll start to notice, and it only gets worse.

The Solution

The solution for this is straightforward; you add a track by to your ng-repeat so that it looks like this ng-repeat="chore in chores track by chore.id".

<div class=“chores-container”>
<table>
<tbody>
<tr ng-repeat="chore in chores track by chore.id">
<td>{{chore.assigned_to}}</td>
<td>{{chore.name}}</td>
<td>{{chore.done ? ‘Done’ | ‘To Do’}}</td>
<td ng-show=“chore.priority”>{{chore.priority}}</td>
</tr>
</tbody>
</ul>
</div>

Simple, right? Can we call it a day and go home?

Why this works

No, we can’t. Any software developer worth his salt needs to KNOW why something works, not just that it does.

When we don’t use the track by, the ng-repeat by default keeps track of items by appending a $$hashKey to each item. Angular goes through the process of removing and adding back everything, when $$hasKey is changed, or a new one is added.

Adding a new array from your server call which won’t have any $$hashKey ‘s in it, causes Angular to trigger ng-repeat to create a whole a new batch $$hashKey’s which triggers the full removal of existing items, and recreation of mostly duplicate items.

When we use the track by, we are telling angular to use a key that we specify. This limits the number of changes to only the new or changed items in the array, which is going to be very helpful when you’re trying to avoid reloading 1000+ items.

--

--