Faster AngularJS or how not to shoot yourself in the foot

Release of AngularJS 2.0 approaches, and performance issues with the first version still remain. This post focuses on the fasterAngularJS applications and will be useful for both beginners and those who already uses the framework, but have not yet encountered problems with performance.

As is known,AngularJS is a declarative frontend  framework that provides a convenient data binding . Of course, let’s not forget about the testability, supportability and conditional readability of AngularJS applications, but in the context of this post, it is not important.

Let’s talk more about scope, digest, and watcher.

Scope (or $ scope) is an object that contains data and / or methods that need to be displayed or used on the page, as well as a number of technical properties, such as its ID, a reference to the parent scope, and so on.

Watcher is an object that stores a value of the given expression and our callback function to be called if expression changes. Array of watchers is in the $scope.$$watchers.

Digest – alternate bypass all of the watchers and the calls of the callback functions, the value of which is changed. If after the result of the digest, at least one value has been changed, the digest will be started again. Therefore digest often run two or more times. If the digest will run more than 10 times – Angular throw an exception.

Watchers are stored in scope, basically, they are created automatically, but you can create them manually. Scope of the directive uses a controller, or they create own scope. Obviously, more watchers you have, the longer cycle digest you will get in the result. And because, javascript language is single-threaded, then the long duration of the digest,  will “slow down” your application. Especially, you should keep in mind, that the digest is not just bypass of the array, but also a challenge for the callback expression whose meaning has changed. It is believed that the angular ensure trouble-free operation as long as the page contains up to about two thousand watcher-s. And, although this figure sounds impressive enough, you can achieve it fairly quickly.

For example here is this little piece of marking ten lines create eighty-watchers plus ten individual scopes:

<table> 
    <tbody> 
      <tr data-ng-repeat="giftstart in model.giftstarts" data-ng-hide="giftstart.isDeleted"> 
        <td>{{giftstart.name}}</td> 
        <td>{{giftstart.description}}</td>
        <td>{{giftstart.releaseDate}}</td>
        <td>{{giftstart.mark}}</td>
      </tr>
    </tbody>
</table>

And now to the practice of faster AngularJS

The first performance problem lies in the number of watcher-s. And to solve it, we must clearly understand that by creating any binding expression, we create a watcher. Ng-bind, ng-model, nd-class, ng-if, ng-hide, and so on – they all create an object-observer. And if, alone they are not a threat, their use with ng-repeat, as seen in the above example, can quickly assemble a small army of killers of our application. But the greatest danger is completely dynamic table. They are able to produce watchers on a scale worthy of Mr. Honda Isiro.

Therefore, the first (and sometimes the last) step in the optimization of the number of watcher-s lies in the analysis of the variability of the data, which are displayed on the page. In other words, it is worth only look at the data that must change. Very often there is a situation where you just need to bring data to the user. For example, you want to display a list of all possible purchases or bring static text, which neither will be affected by user or server. Therefore, in the angular 1.3 is now a special syntax for the single data binding, which looks like this:

data-ng-bind=”::model.name”

or

data-ng-repeat=«model in ::models»

This expression means that, once the data are calculated and displayed on the page, watcher responsible for this expression is deleted. Combining the one-time binding with ng-repeat can get substantial savings of watchers in our application. But, if for example the server sent null instead of the product name the watcher will not be deleted. He will “wait” for the data, and then be deleted.

The second step is to divide responsibilities. In other words – not everything has to be Angular. For example, we can use directive ng-class to set the CSS class to not even or even elements of array. Replace it with a CSS rule tr: nth-child (even) (example). We get rid of excess watchers, also received a prize (very small) in speed. The situation is similar with events such as ng-mouseover and ng-mouseleave. Often, their treatment can be entrusted to a directive plus jQuery. Speaking of jQuery and directives. Sometimes, a table or list to be redrawn in only one or two cases. In this case much more efficient to use a directive, together with one or two manually create a watchers. If any functionality is not to about redrawing the data model – this is the first sign that you using Angular style of building app here. It is not always necessary, but the decision should be made aware.

Here is a simplified example. Let we have two lists of products – available ones, and those that are selected by the user. Obviously, the first list will be  large and static, since after it’s downloaded, the user or server will not change be able to change it. So, here we can use “one and done” ng-repeat. But the second list is dynamic and constantly changing by user. So we should avoid using one way data binding here. Although, if the actual data not needed every second, but only when you click “buy”, it’s possible to make static list, placing the responsibility for the collection of data on directive.

Finally, the third step is to correct hiding unused markup. Out of the box, AngularJS provides ng-show / ng-hide directives to hide or show  parts of the page. However, the associated watchers do not disappear and involved in the digest as before. So it’s better to use of ng-if, which completely cuts out elements from DOM tree together with the corresponding watchers.  Although, removing items from the DOM is also not the fastest procedure, so use ng-if is to those parts of the markup that will hide / show not too often.

The second, though less of a problem with the performance is too often invoked digest.

Typically, problems with digest occur when the number of watchers is approaching a critical, but if you often run the digest you can also create tone of problems. As we already know, digest is not only bypassing array of watchers, but also perform callback for changing expressions. In addition, often the digest is run several times, slowing down the performance. Ng-model will start to digest after each letter entered. For example entering the word of fifty-five letters will launch digest at least one hundred ten times. Once the user enters the first letter digest will be launched . Since, in the process of its implementation, Angular will found that data changed, the digest will be executed repeatedly. Incidentally, the digest will be caused not only in the scope of the controller, but also on other pages scope. Therefore ng-model can be quite a serious problem.

A simple solution would be to add a debounce-parameter to the call of the digest for the specified time. A similar situation with ng-mouseenter, ng-mouseover and so on. They can start to digest too much, leading to a drop in application performance. Therefore, you must pay special attention to areas in which user can invoke the digest in the application, such as input, textarea and so on. And if you need to call digest manually, try to do it when the maximum number of changes made that would digest caught them all in a single pass.

The following is a list of interesting, in my opinion, points that can also improve application performance:

  • If possible, use ng-bind instead of {{}}. The string binding expression is processed approximately twice as slow compared to ng-bind. In addition, the use of ng-bind eliminates the need to use ng-cloak.
  • Avoid using complex functions in binding expressions. Options specified in the binding expressions run each time you start the digest. And because digest often run repeatedly, these functions can significantly slow rendering of the page.
  • Use filters only if you can not do without them. If the functions specified in the binding expression is one time to digest, the filter function is performed twice in the digest for each expression. It is best to filter the data in the controller or service.
  • If possible, use the $scope. $digest() instead of $scope. $apply(). First function will launch digest function only within the Scope on which it was called, and the second – on rootScope. Obviously, the first digest will be faster. Incidentally Angular $timeout function causing $rootScope.$apply().
  • Be aware of the possibility of a pending call digest on user input, setting the parameter debounce: data-ng-model-options = “{debounce: 150}”
  • Try to avoid using ng-mouse-over and similar directives. Call of this event starts the digest, and the nature of such events is that they can be called repeatedly in a short period of time.
  • When you are creating watchers, do not forget to maintain the function of removing them and call it as soon as the watchers will no longer be needed. Also, avoid placing objectEquality flag to true. This causes a deep copy and a comparison of old and new values ​​to determine whether to call callback functions.
  • It is not necessary to store a reference to DOM elements in scope. It contains links to parent and child elements, in fact, the whole DOM element. And, then, the digest will run across DOM tree checking which object has changes. Expensive.
  • Use parameter track by in ng-repeat directive

Useful links:

Tip #4: ng-bind is faster than expression bind and one time bind

6 Common Pitfalls Using Scopes

Speeding up AngularJS apps with simple optimizations