Angular 1.x Performance Quick Tip: One-Time Binding

Juan Carlos Garzon
iCapital Network Technology Group
5 min readDec 5, 2018

--

Photo by Chris Liverani on Unsplash

As software developers, performance is often something you don’t worry about until you have no choice. This rings true particularly with Front End performance. Full Stack developers like me tend to pay much more attention to optimizing API’s. That’s all fine and dandy if your API takes 2ms… until you realize that your UI is lagging.

I’d like to use this space to shine a spotlight on a quick and easy method toward maintaining and increasing the performance of your Angular 1.x legacy applications.

Note: Angular 1.x is on its way out in favor of React and the later versions of Angular. Migrating large codebases often happens in phases and these tips are meant to help optimize and maintain your Angular 1.x. legacy applications.

Background

At its core, Angular’s bind data system is easy to use. This system (usually) automatically synchronizes the data between the model and the view. Angular essentially treats the model as the single source of truth.

Data Binding is typically the first skill we are introduced to when learning Angular. You can recognize it anywhere you see double brackets syntax (i.e. {{}}) surrounding your model or variable in the view. Data binding is also used as HTML attributes such as the ng-model. No matter which method is used, the outcome is always the same. Data binding dynamically updates the view and synchronizes user inputs, changes or interactions with the model and controller respectively.

This data binding is usually not a problem. However what Angular’s own tutorial leaves out, is how this data binding can become problematic with larger and more complex the application becomes.

You can happily work with Angular and never spare a single thought how it performs. I do recommend you spend some time learning how Angular works, particularly the digest cycle. I won’t delve into details in this article, but in essence, the digest cycle is the means by which Angular synchronizes data and ensures that everything is updated.

The digest cycle is a loop that loops over a collection of watchers. A watcher is a listener that triggers the update of the model or view. The digest cycle loops over watchers repeatedly until it determines that no additional changes are required. A watcher is created each time we bind something. There is a lot more to this process than I’ve described, but this is the fundamental concept you should grasp.

The more we increase the complexity of our applications, the more watchers we create. This, in turn, increases the duration of our digest cycles. This is where performance issues will arise; these issues are exacerbated once you learn that the digest cycle must run at least twice.

The digest cycle must run at least twice because it must take into account that data may be interdependent. If one element is changed, this can potentially trigger a change in another element and set off a chain reaction of changes. For this reason, Angular must continue the cycle until it detects that no additional changes have to be made.

Note: if the loop runs ten or more times, Angular will throw an exception and kill the app. It does this to prevent and infinite loop scenario.

Visualizing the Problem

// Angular Controller
$scope.chores = [
{
assigned_to: ‘Bobby’,
name: ‘Homework’,
done: true,
priority: ‘High’
},
{
assigned_to: ‘Chris’,
name: ‘Wash the Dishes’,
done: false,
priority: ‘Low’
},
{
assigned_to: ‘John’,
name: ‘Walk the Dog’,
done: true,
priority: ‘Medium’
}
//HTML
<table>
<thead>
<tr>
<td>Assigned To</td>
<td>Chore</td>
<td>Status</td>
<td>Priority</td>
</tr>
</thead>
<tbody>
<tr ng-repeat=”chore in chores”>
<td>{{chore.assigned_to}}</td>
<td>{{chore.name}}</td>
<td>{{chore.done ? ‘Done’ : ‘To Do’}}</td>
<td>{{chore.priority}}</td>
</tr>
</tbody>
</table>

Let’s look at an example. How many watchers are created above?

You should promptly notice that there are 4 bound items in a table. We can see chore.assigned_to, chore.name, chore.done, and chore.priority. Did you notice that there is an ng-repeat?

This means Angular is binding those 4 properties for each chore in the array. With that being the case, we have 4 properties x 3 items, for a total of 12 watchers. If we stopped counting there, we’d be close… but we’d be wrong. We are missing the chores array in the ng-repeat. In actuality have a total of 13 watchers, for a relativity simple table view.

Observe how, even in this relatively simple example, we create more watchers than we initially think. You can safely assume that just about everything you do with Angular creates a watcher. Think about all the Angular attributes you use, directives you create, or even the third-party components you take advantage of. Now, think about all the arrays you are using. Each of those will create a watcher. It should become apparent that your performance could, as a result, take a big hit.

Quick Solution

A lot of the data on a page is usually static. It need not change; it just needs to be displayed. In other words, once the data is displayed, a watcher is no longer necessary.

Here is where Angular’s One-Time Binding saves our application from holding on to watchers it doesn’t need. You could easily tell Angular, “Once this is loaded, don’t worry about it anymore.” The best part is that it doesn’t require a massive change to your existing views. You just need to add :: before whatever you’re binding. For example: instead of inputting {{variable}}, you would input {{::variable}}.

// Angular Controller
$scope.chores = [
{
assigned_to: ‘Bobby’,
name: ‘Homework’,
done: true,
priority: ‘High’
},
{
assigned_to: ‘Chris’,
name: ‘Wash the Dishes’,
done: false,
priority: ‘Low’
},
{
assigned_to: ‘John’,
name: ‘Walk the Dog’,
done: true,
priority: ‘Medium’
}
//HTML
<table>
<thead>
<tr>
<td>Assigned To</td>
<td>Chore</td>
<td>Status</td>
<td>Priority</td>
</tr>
</thead>
<tbody>
<tr ng-repeat=”chore in ::chores”>
<td>{{::chore.assigned_to}}</td>
<td>{{::chore.name}}</td>
<td>{{::(chore.done ? ‘Done’ : ‘To Do’)}}</td>
<td>{{::chore.priority}}</td>
</tr>
</tbody>
</table>

It truly is that simple. Notice you can use :: within the ng-repeat. You are not limited to using :: to only double brackets. Additionally, remember that if you have some inline logic you can just encase it in parentheses, i.e. (::(chore.done ? ‘Done’ : ‘To Do’)).

When not to use a One-Time Binding?

One-time binding is not something to use without careful thought. If you have an element, model, or variable that changes on-screen, then one-time binding is not an ideal choice. Think of one-time binding as a freeze ray. Once it’s rendered on-screen and bound variable contains a value. The view will not display the updated result… no matter how much you change it. This makes one-time binding exceptional for static page elements, but inappropriate for interactive or dynamic elements of a page.

Who Watches the Watchers?

Since we don’t exist in a comic book world where people are constantly worried about the unchecked power held by costumed vigilantes, this actually has an answer. There are extensions you can download for Google Chrome that will give you a count of the watchers. These extensions will also show you first-hand how much of an impact these.

https://chrome.google.com/webstore/detail/angularjs-inspect-watcher/gdfcinoagafkodbnkjemaajfahnmfkhg?hl=en

https://chrome.google.com/webstore/detail/angular-watchers/nlmjblobloedpmkmmckeehnbfalnjnjk?hl=en

--

--