Check out myAngular article series with live demos and Facebook group Angular - Advanced Topics

Removing watches in AngularJS applications

Torgeir "Tor" Helgevold
- JavaScript Developer and Blogger
Published: Sun Nov 30 2014

Angular is a great framework for composing complicated data driven user interfaces. Most of the time Angular does a good job of balancing flexibility and performance, but in large complicated UIs, performance may become an issue if you're not careful. In this post I will discuss some effective techniques that may help you tune your application.

A potential performance bottleneck in Angular is introducing too many watches as a result of data binding between your model and the html. Most applications will not suffer from this since you really need a large number of watches before you start to experience problems.

Here are a few simple techniques for reducing the number of unnecessary watches.

1) One time binding:

Angular will by default watch any bound property and attach a two way binding between the data object and the UI element. In many cases the two way binding is unnecessary, especially if you only need the binding for the initial rendering of the UI. In version 1.3 the Angular team introduced a one time binding as a way to opt out of two way binding in cases where it's unnecessary. Below you can see an example of a one time binding.

{{::customer.name}}

Notice the :: syntax which will prevent Angular from tracking changes to the customer.name property.

2) ng-repeat

ng-repeat is a powerful directive, but it may also earn you a lot of extra watches if the backing array is large. It's important to realize that every iteration through the repeat will earn you a new watch. Keep in mind that this is in addition to watches introduced by bindings in the repeated content. You should also keep in mind that watches introduced by the repeated content will be multiplied by the number of iterations through the repeat.

The good news is that you can leverage one time bindings in ng-repeats as well. In the code below I have added an ng-repeat with a one time binding on the number of customers and the names of customers, but I have enabled two way binding on individual customer addresses. This is very helpful since it shows that we can mix and match tracked elements to fit our needs.

<a ng-repeat="customer in ::customers" > {{::customer.name}} {{customer.address}} </a>

3) Watch the sum and not the individual parts

It may be tempting to render content from multiple distinct fields, but in most cases it is more efficient to combine the fields in your model and watch the combined field instead.

In the example below I am showing that in order to save watches it's better to combine first name and last name into a single fullName property instead of introducing watches for each part.

{{customer.fullName}} //do this {{customer.firstName}} {{customer.lastName}} //don't do this

4) ng-if vs ng-show

ng-if and ng-show are meant for showing or hiding content based on some condition. However, there is an important difference in that ng-if will remove hidden content from the dom whereas ng-show will simply hide it using styles. This means that watches in content hidden by ng-show will still be active since they are still present in the dom. You can therefore, in some cases, reduce the number of watches if you use ng-if instead.

5) Use two simple templates instead of a single complicated template

It may be tempting to combine several templates into one and use multiple conditionals to hide and show content throughout the view. However, it's important to remember that the conditionals will add watches, so if you need to save watches, it may make more sense to use two separate templates to get rid of the branching.

6) ng-include

ng-include is a common way of including external templates in a parent view like in the example below

<div id='parent'> <div ng-include src="'views/templates/myTemplate.html'" /> </div>

However, the thing to remember is that Angular still adds a watch to src even if the view string is hard coded.

If your content is truly static, with no need to update the src of the bound template, a potential workaround to save a watch is to create your own static directive like below.

angular.module('myApp').directive('staticInclude', [function(){ return { restrict: 'AE', templateUrl: function(elem,attrs){ return attrs.source } }; }]); //Use the directive like this: <div static-include data-source="myView.html"></div>

The difference between the custom static include and regular ng-include is that you avoid an unnecessary watch on the hard coded url.

Another observation I've made is that the ng-include directive will add watches to all top level elements in the included content. This means that in order to save watches you should avoid having multiple top level elements, and instead ensure a single root in your template.

In other words: Avoid "ng-including" content like this:

<div>element1</div> <div>element2</div> <div>element3</div>

Instead you should wrap it in a common root element

<div> <div>element1</div> <div>element2</div> <div>element3</div> </div>

7) Avoid duplicated bindings

Another good advice is to avoid duplicated bindings that can be rolled up into a single binding.

//Avoid this pattern of duplicated bindings <div ng-if=”isActive”>Element1</div> <div ng-if=”isActive”>Element2</div> <div ng-if=”isActive”>Element3</div> //Instead, roll the check up into a single binding <div ng-if=”isActive”> <div>Element1</div> <div>Element1</div> <div>Element1</div> </div>

As you can tell from the example above, three ng-ifs are rolled up into one to save watches.

If you liked this article, share it with your friends on social media:

We also have a new Facebook group about advanced Angular topics.

I invite you to follow me on twitter