Thursday, April 9, 2015

Improving Angular.js performance using One Time Binding

In the previous post I explained how the $digest loop works and how to increase performance using the ng-model-options directive. In this post we’ll take a look at ‘one time binding’ feature.

As I mentioned before the $digest loop goes through all the registered watches every time in order to check whether they need to be updated. However not all our scope variables and models should have binding or be updated after they have been initialized, for example: a logo path, a title, names, some constant options that we used ng-repeat for them and so on. All those variables should be initialized and not to be updated until a full page refresh.

One time binding is a feature that was introduced along with ng-model-options in Angular 1.3. In order to set that a scope variable should not be updated all you have to do is add :: before the variable name:

<input type="text" ng-model="name" value="text" />
{{::name}}
{{name}}


Adding it before the scope variable tells Angular not to register a watch on the variable and so decreases the amount of watches for the $digest loop to check which leads to increase in the app’s performance.

Enjoy

Improving Angular.js performance using ng-model-options

Every app has a search ability. Some apps wait for the user to hit “Enter” and then send the search string to the server, others (especially when using Angular) bind every user’s key stroke to a request to the server. In my opinion the second option has better user experience since the user starts to receive results while he is typing and most likely will get what he is searching for before he finished typing the whole search string and he is no more required to press another button after he finished typing.

However if you have a large Database and your search request take more than several milliseconds then there is no use to send a request to the server after every keystroke – the user will not get the relevant results. So how do you control when to send the request to the server?

Ng-model-options

Ng-model-options is a directive that was introduced in Angular 1.3. It has some really nice features:

updateOn:

With the updateOn parameter, we can define which events our input should be bound to. For example, if we want our model to be updated only after the user removed the focus of our input element, we can simply do so by applying the ngModelOptions with the following configuration:

<input type="search"
ng-model
="searchQuery"
ng-model-options
="{ updateOn: 'blur' }"/>

Debounce:


debounce defines an integer value which represents a model update delay in milliseconds. Which means, if we take the example mentioned above, that we want to update our model 300 milliseconds after the user stopped typing, we can do so by defining a debounce value of 300 like this:


<input type="search"
ng-model
="searchQuery"
ng-model-options
="{ debounce: 300 }" />

Advanced options: we can configure how the update delay should be done for different events. Controlling the debounce delay for specific events can be done by defining an object instead of a integer value, where keys represent the event name and values the debounce delay. A delay of 0 triggers an immediate model update.

The following code generates a model update delay of 300 milliseconds when the user types into our input, but an immediate update when removing the focus:


<input type="search"
ng-model
="searchQuery"
ng-model-options
="{ updateOn: 'default blur',
debounce: { 'default': 300,
'blur': 0 } }"
>

How does it work?

The ng-model-options controls how the ngModel is updated.

By default Angular automatically registers a watch for every scope variable for updating the binding, the update itself happens due to the $digest loop that is triggered at every key stroke. The $digest loop checks if the model has a new value and if so it updates every model instances in the app without making us add event listeners or manually update the DOM.

As you can see this means that the $digest loop has to go through all the registered watches on the scope at every key stroke and depending on the watches callbacks that can be very expensive. So the debounce triggers the $digest loop after the defined milliseconds allowing the user to type a meaningful search phrase and reduce the amount of the $digest loops.

In the next post we’ll see how ‘one time binding’ also increases our app’s performance.





Ng-Hint – Intellisense for Angular.js

Angular is great. I love it and in my opinion is the best thing that happened to the web development community in the last several years. However as much as I love Angular there is nothing more annoying than searching all other your code while trying to figure out why the #@$% it doesn’t work! And then finally you see it - you forgot a module or misspelled ‘ng-repeat’. If only Angular would have some sort of intellisense what would warn me when I make those mistakes.

Angular-Hint

Is a great library that scans your code and writes warnings to your browser console.

It has several modules:

clip_image002

  • angular-hint-directives - Hints about misspelled attributes and tags, directives and more:
    <ul ng-repaet="point in points">
    <li>{{point}}</li>
    </ul>

The console will show this hint:

clip_image004

  • angular-hint-dom - Warns about use of DOM APIs in controllers. Angular best practice is to avoid using DOM API from the controller so that this code will trigger a warning:

angular.module('sample').controller(function(){
//Accessing the DOM API `document.getElementById()` triggers an AngularHintDOM warning
var list = document.getElementById('list');
var newListItem = document.createElement('div');
newListItem.innerHTML
= 'Item 1';
list.appendChild(newListItem);
});

clip_image005

angular-hint-events - Identifies undefined variables in event expressions. For example if you have a ng-click targeting some function that doesn’t exists in your controller : 

<a ng-click="increments">Count!</a>
$scope.increment = function(){
++$scope.count
};
 The docs say that this code should trigger a hint, unfortunately it didn’t work for me Sad smile – they have an open issue for that.


var myApp = angular.module('myApp', []);

I’ve also included :


<script src="src/angular-messages.js"></script>

which has a module in it. So the console has a warning:

clip_image006

The first warning is a known issue and should be ignored.

This is a great library however is still has some open issues and the worst part is that if you reference the script hint.js it will crash your app Sad smile. They have several open issues addressing this problem. I truly hope that they would fix it soon since it is a great library. Meanwhile you could add the script before you commit your work just to see if there are no warnings and you have applied all the best practices.

Hint: Keep your eye on Angular-Hint!