AngularJS Performance Tweaks

This is a short post on AngularJS performance tweaks that will help you to write efficient AngularJS code. Be aware that this short list is – potentially - non-exhaustive and might be enhanced over time.

General stuff

  • Keep number of watchers < 2000
    To measure the amount of watchers used in an Angular app, there is a pretty good code-snippet that can be easily integrated into Chrome called ng-stats.

  • Prefer ng-if over ng-show/ng-hide
    It strongly depends on your use case but always keep in mind, that elements with the ng-hide attribute are fully rendered and all DOM elements are created, even if the user cannot see anything. I would recommend reading this answer in StackOverflow for a better understanding.

  • Avoid deep-watchers
    Most of us probably won't do this, but it's good to keep in mind that something like deep-watchers exist but should only be used in reasonable cases.

// example of a deep watcher (see true as third argument)
$scope.$watch('data', function (newVal, oldVal) { /*...*/ }, true);
  • Prefer $scope.$digest over $scope.$apply
    While $digest only starts a digest cycle for the scope it is called upon, $applytriggers a digest-cycle on the $rootScope. This means that all watchers are checked instead of only the watchers in the given scope being checked (also see this article).

  • Use $evalAsync() when triggering the digest cycle manually
    Sometimes you must trigger the digest cycle manually resulting in an error message saying: Error: $digest already in progress.

$scope.$evalAsync(function () { 
  // do something that requires dirty checking of data 
});

Repeater

  • Use track by in ng-repeat especially for complex objects
    If data is reloaded in the collection, Angular will rebuild the DOM for all those elements. That won't happen, if you track your objects for example by their id, since Angular would know that the object is already represented in the DOM.
<div ng-repeat="obj in ObjectCollection track by obj.id">  
  {{ obj.name }}
</div>  

Also read this for detail information.

  • Do not use inline methods in ng-repeat
    Angular will re-render all items in the collection returned by the inline method with every digest cycle. Pretty bad idea!
<div ng-repeat="obj in getAllObjects()"> <!-- don't do this -->  
  {{ obj.name }}
</div>  
  • Use the bindonce package for large lists
    When rendering large lists with data that won't be changed the bindonce package can help a lot to improve the performance of the app. See the Github page of the project for detailed information.

Data bindings

  • Use one-time data bindings whenever possible
    Instead of binding all variables in templates and keeping watchers for those bindings, it often is enough to use one-time binding and thereby reduce the amount of watchers drastically.
// example 1: one-time binding for the color variable
<div name="attr: {{::color}}">text: {{::name | uppercase}}</div>

// example 2: one-time binding for the items collection
<ul><li ng-repeat="item in ::items">{{item.name}};</li></ul>

// example 3: one-time binding for the name variable
<p>One time binding: {{::name}}</p>

// example 4: ng-if only evaluated once
<div ng-if="::user.isLoggedIn">Information for logged-in user</div>

// example 5: ng-class only evaluated once
<div ng-class="::{ loggedIn: user.isLoggedIn }"></div>  
  • Prefer pre-filtered values over filters in templates
    When using filters in templates, the filter is applied with every digest cycle resulting in a potentially serious impact on the overall app performance.
<p>{{ user.signature | strip | reformat }}</p> // don't do this  
<p>{{ filteredUserSignature }}</p> // pre-filtere value in controller  
<p>{{ ::filteredUserSignature }}</p> // even better with one-time binding  

Disabling debug mode

A often neglected performance bottleneck is the debug mode of AngularJS. As the author of this article figured out, even bigger websites have the debug mode enabled leading to a less performant web application and not bringing any benefit while in production.
To easily disable debug information in your production app, just configure it in the application's config phase.

myApp.config(['$compileProvider', function ($compileProvider) {  
  $compileProvider.debugInfoEnabled(false);
}]);