Last updated at Fri, 03 Nov 2017 17:22:07 GMT

AngularJS can have performance problems when you start to scale your application. You may notice that your AngularJS application works well at first but as it grows in complexity, so does its load time. Maybe it hangs for a second (or more) on initial load and when its fully loaded, it lags. If this sounds familiar, this article is for you.

tips-optimizing-angularjs-app

There are several reasons your AngularJS application may be slowing down – here’s a list of solutions to the most common issues.

Reduce Watchers

Reduce the number of watchers in your application. A big cause of slowness in AngularJS applications is having a large numbers of watchers. AngularJS uses dirty checking to check the state of the application by evaluating every watcher and applying changes in what is called the digest cycle. The more watchers you add, the longer you make your digest cycle. 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.

So when are watchers set? The main examples are:

  • {{ }} bindings
  • In some directives (ng-show/ng-hide)
  • Scope variables
  • Filters (when piped)
  • ng-repeat
  • $scope.$watch

These watchers will be evaluated on every digest cycle, started by any of the following:

  • ng-change
  • ng-model changes
  • $http events
  • promises being resolved
  • $timeout or $interval
  • Manual calls to $scope.apply and $scope.digest
  • One-time binding

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 adequate. In some cases, it will need to be dynamically calculated and changed, but in many cases you can rely on one-time binding, setting the values after an initial calculation. This can be achieved with {{::value}}.

Ng-repeat

Ng-repeat is an incredibly powerful part of AngularJS and often easy to abuse and misuse. Having nested ng-repeats is a quick and easy way to increase your watcher count exponentially and should be avoided if at all possible.

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 (better still, 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. Pagination is a good way of limiting the amount of content you need to display and therefore need to watch.
If you are applying filters to the arrays, it’s often best to do this in your javascript as opposed to piping it in the HTML.

Example of a limit filter:
$filter('limitTo')(array, limit)

Example of tracking by index:
<li ng-repeat="item in array track by item.id></li>

Limit HTTP requests

Every response to a http request will cause a digest cycle to kick off. A way to limit them would be to create endpoints to do bulk requests when needed, changing multiple indexes in one call, requiring only one digest cycle.

Debounce your ng-model

If a user is typing in an input field assigned to a ng-model, every keystroke is a change to the model which in turn causes a digest cycle. 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 can significantly improve the user experience as the user will not experience any lag when typing.

Debouncing is easy to do in AngularJS as ng-model has a option to allow debouncing:
ng-model-options="{ debounce: 100 }”

In the example above, the model will only be updated if no further change happen for 100ms.

Ng-if or ng-switch instead of ng-show

Ng-show & ng-hide use CSS to hide the node when applicable, meaning the node is always there and therefore included in any calculations done in the digest cycle. Conversely, ng-if & ng-switch remove the node from the DOM, thus reducing unnecessary calculations. While this may not always be appropriate, a node that will be frequently toggled will likely be just as expensive to remove each time.

$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.

Abiding by the above guidelines can help significantly improve your AngularJS application’s performance. Let us know of any other ways you optimize your application in the comments below.