Angular JS series – How to optimize the performance of your AngularJS applications

Angular JS series – How to optimize the performance of your AngularJS applications

Angular JS series – How to optimize the performance of your AngularJS applications

 

Introduction

A good understanding on how AngularJS works – see our previous AngularJS blogposts – will give good insides on how good practices. It can happen that AngularJS projects start to grow – and performance drops are noticed. A collection of some tips & tricks to keep in mind when symptoms start arising.

General guidelines say the following about application slowness:
$apply > 25ms
• Click handler > 100ms
• Show a new page > 1s
• Reaching above 10s many users will give up
• Around 200ms is the most ideal

Below you can find a list of tips on how to improve your AngularJS application performance.

 

Measuring performance

Discover performance eating monsters by measuring the AngularJS application performance. We have written a dedicated post about out-of-the-box tools for measuring your AngularJS application performance.

 

Reduce watchers / binding once

The more watchers you add, the longer the digest cycle will need to evaluate. If a cycle is over 16ms, your UI will likely be very sluggish. 16ms is the max length if you want to maintain 60fps in your application.

When deciding on the bound data in your application, you should consider if it needs to be two way binding or if the initial loading is sufficient, binding only once. Angular will wait for a value to stabilize after its first series of digest cycles, and will use that value to render the DOM element. This can be achieved with {{::value}} (AngularJS 1.3+).

Changing binding from two-way data binding to a one-way data binding (or single data binding) approach can drastically improve performance. The One-way data bindings are only evaluated once, and not kept as watches for updates … Data are ‘static’ can be added as single data bindings on your view. Data that needs / will be updated in the lifetime of your Angular application should be added according the two-way data binding approach.

As a quick illustration showing that one-way data bindings are only evaluated once – you will see that updating the vm.movieName will not trigger view updates when added using the {{::}} approach.

 

Ng-repeat directive

The ng-repeat directive of AngularJS is getting slow above 2500+ two-way data bindings. This is due to AngularJS “dirty checking” approach. Every watch consumes time, so large lists with complex data structure will slow your application down. Avoid unneeded use of ng-repeat.

You should also avoid functions in your html that attempt to determine the array to use in the ng-repeat. These functions need to recalculated even when there is no change, unnecessarily reloading the ng-repeat each time. Instead, try limiting the array to a fixed number and then track the ng-repeat by the index (event better is to give each item a unique id). Doing this eliminates the need to create nodes in the DOM each time, it just changes values if needed (re-use).

/* tracking on unique id */
<li ng-repeat="item in filteredItems track by item.id">
    {{ item.name }}
</li>

Pagination is a good way of limiting the amount of content you need to display and therefore reduce the amount of watchers.

 

Prevent inline methods for data calculation

Do not use a method for getting the filtered collection. Ng-repeat evaluates the expression on every $digest. In our example the filteredItems() returns the filtered collection. If this evaluation is slow, it will quickly slow down the whole application.

/* prevent calling data from direct function invokes */
<li ng-repeat="item in filteredItems()">
    {{ item.name }}
</li>
/* better approach – bind filteredItems to the view model (scope) */
<li ng-repeat="item in filteredItems">
    {{ item.name }}
</li>

 

Use ng-if instead of ng-show / ng-hide

If you use directives or templates to render additional information, be sure to use ng-if (AngularJS 1.1.5+). Ng-if prohibits rendering, in contrast to ng-show, which is just hiding the content with CSS (ex: display:none). Therefore the additional DOM elements and data-bindings are only evaluated on demand.

 

Use $log service for debug

Always use the $log service for debug logging. You can switch the $log service debug method off when you move your application to production.

app.config(function($logProvider){
  $logProvider.debugEnabled(true);
});

 

Limit $http requests

Every response to a http request will cause a digest cycle to be invoked. A way to limit them, would be to limit the http requests.

 

Debounce the ng-model

If a user is typing in let’s say an input field assigned to a ng-model, every keystroke is a change to the linked model, which in turn causes a digest cycle invoke.

In most cases, you could benefit from debouncing. This means you only apply the changes if the user stops typing for a certain period of time.

Debouncing is fairly easy to integrate. Ng-model has a option to allow debouncing. In this example, the model will only be updated if no further change happen for 100ms. ng-model-options=”{ debounce: 100 }”.

 

$watchCollection instead of $watch

You can get AngularJS to perform deep checking of an object using the third parameter of $watch. This gets it to evaluate every property in an object. For deeply nested objects, this can be expensive. A better alternative is to use $watchCollection, which limits deep checking to the first layer (which in most cases is enough) and could make major improvements to your performance.

 

Use $digest instead of $apply

$apply calls the digest cycle from the $rootScope down to all attached $scopes. This is not always needed.

 

Use ng-if instead of ng-show / ng-hide

Normally AngularJS adds some things like CSS classes and scope related properties to the DOM elements. This is not really needed to run the application and is really only done to help development tools like Protractor or Batarang. When the application is in production you can save some overhead by disabling this using the $compileProvider.debugInfoEnabled() function.

/* disable debug info on config block */
angular.module('app')
   .config(['$compileProvider', function($compileProvider){
       $compileProvider.debugInfoEnabled(false);
}]);
/* console command for reloading application with debug info */
angular.reloadWithDebugInfo();

 

Disable comment and CSS class directives

Nowadays most of the Angular projects are using only element and attribute directives, and in such projects there is no need to compile comments and classes.

If you are sure that your project only uses element and attribute directives, and you are not using any 3rd part library that uses directives inside element classes or html comments, you can disable the compilation of directives on element classes and comments for the whole application. This results in a compilation performance gain, as the compiler does not have to check comments and element classes looking for directives.

To disable comment and css class directives use the $compileProvider:

/* disable comment & css on config block */
angular.module('app')
   .config(['$compileProvider', function($compileProvider){
       $compileProvider.commentDirectivesEnabled(false);
       $compileProvider.cssClassDirectivesEnabled(false);
   }]);

 

ngBind vs {{}}

ng-bind is about twice as fast as an expression bind through the curly brackets method {{}}. Even with one time binding {{::}}. Use ng-bind whenever you can to reduce the initial rendering time.

 

Use console.time to benchmark function durations

Starts a timer you can use to track how long an operation takes. You give each timer a unique name, and may have up to 10,000 timers running on a given page. When you call console.timeEnd() with the same name, the browser will output the time, in milliseconds, that elapsed since the timer was started.

/* start time ‘test’ */
console.time('test');
/* stop time ‘test’ */
console.timeEnd('test');
/* stop time output ms */
test: 8254.471ms

 

Use Chrome Timeline and Profiler to identify performance bottlenecks

Use the Chrome DevTools Timeline panel to record and analyze all the activity in your application as it runs. It’s the best place to start investigating perceived performance issues in your application.

https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/timeline-tool?utm_source=dcc&utm_medium=redirect&utm_campaign=2016q3#user-produced-timeline-events

 

Prevent using $$ functions

$$ means private context within AngularJS (core functions), so be aware that the interface is not stable. They may change over versions, so prevent using them if possible.

 

Directive clean-up

Directives should clean up after themselves. You can use element.on(‘$destroy’, …) or scope.$on(‘$destroy’, …) to run a clean-up function when the directive is removed. See above section ‘Directives’.

What shall you clean in the directive clean-up handler? It all depends on the directive context. A normal case is that a directive is destroyed because ng-view changes the current view. When this happens the ng-view directive will destroy the associated $scope, sever all the references to its parent scope and call remove() on the element.

Some points of attention, to manually foresee garbage collection:

• Unregister listeners on the $rootScope. Since the $rootScope is never destroyed during the lifetime of a AngularJS application.

• When using $interval or $timeout, be sure to manually cancel them.

scope.$on('$destroy', function () {
  $interval.cancel(promise);
});

• Callbacks to services (providers)
• If your directive attaches event handlers to elements for example outside the current view, you need to manually clean those up as well:

var windowClick = function () { ... }
angular.element(window).on('click', windowClick);

scope.$on('$destroy', function () {
  angular.element(window).off('click', windowClick);
});

• Callbacks from service functions (providers in general)

 


About the Author

Daan is a Creative-Geek who loves learning and sharing new techniques! Follow him on Twitter to keep up to date with the Creative-Geeks blog and other subjects. Contact him on e-mail : info[at]creative-geeks.com.