Tuesday 22 August 2017

AngularJS $watch() ,$digest() and $apply()

This post is meant for novice AngularJS programmers to enlighten them with basic comprehension of how data-binding works. To deep-dive into data-binding, you need a clear vision of how $watch(), $digest(), $apply() and dirty checking works.Lets go step by step on this topic.

Do folow us on FB pageTwitter or G+ for more updates and learning.

"It is not about doing so much as understanding the process itself"

The browser behavior on events:
Generally, browser waits for user interactions, eg: button click event or change events. If you click on a button or select a checkbox, the event’s callback function will run inside JavaScript where all the DOM manipulations will be completed. Browser will get notification to render appropriate changes in the DOM after the callback is completed.

Angular extends this events-loop into a vital concept known as angular-context. To explain what this context is and how it works we will need to explain more concepts.

$watch
When you create a data-binding in your view, AngularJS internally creates a $watch in the $watch list . It means it will detect the changes in the modal it is watching.
Eg:

$Watch example Angular JS
Here we are having $scope. 
customerName which is bound to the first input and $scope.customerId which is bound to second input. 

Hence, we have created two $watch in the $watch list.

WatchList example Angular JS
         
Guess how many $watch are created here?
2 –Wrong(For customer name and id)
3-Right (2 for customer name,id and 1 for ng-repeat)

So if we have 5 customers in a list, the $watch count will be 1+(2*5) i.e. 11
So all the bindings in UI creates a $watch.

But now the question arises is when $watch created for ng-repeat or other directives are?
When template has loaded, the compiler starts looking for every directive and subsequently creates required $watch.

$digest (Extended event loop)
The $digest loop will get invoked when the browser receives an event which can be managed by angular context. This comprises of two inner loops. One process the $evalAsync queue while the other keeps an eye on$watch list of the current scope and its children checking following process:

Hey $watch, what is your value?
I am “Selenium”.
Is it changed?
No, Boss.
No action will perform on this, it will iterate to the  next.
Buddy, what is your value?
          It is “User Friendly Techy Help”.
Is it changed?
Yes, It was “Unified Functional Testing”.
Great, now we have to update the DOM.

The $digest() keeps calling the all the watchers that were being created and then make changes in the DOM.
This process is called as dirty-checking

Key Points:

  • If any one of them has changed, the loop will run again until all of the $watch, report no changes to ensure that the model is clean.
  • This function will throw 'Maximum iteration limit exceeded.' if the number of iterations exceeds 10.
  • Usually, $digest is never called directly, instead we use $apply() which will force a $digest().
  • The significant point is that “All the events” that enters the angular context will fire a $digest loop. Eg:

appcontroller example in TestNG

In this example, we have only two $watch as ng-click doesn’t create any $watch(the function will not change).

Who notifies which event enters the angular context and which one do not?
Answer is: $apply
Whenever an event is fired, it will go through the angular-context and fire $apply(), else it will run outside it. In earlier example: You may have noticed that it works without calling $apply(). The reason behind this is Angular called the $apply method internally.

So, when do you need to call $apply()? 
There are some corner cases where AngularJS does not call the $digest() function for you. You will usually observe that by checking that the data bindings do not update the displayed values.Actually, the events never enter the angular context and the $digest loop is never fired. Be sure you call, $apply() if you need to run a $digest loop to update your DOM.

Key Points:

  • AngularJS calls almost all of your code within an $apply call. So you need not to call it explicitly.
  • Calling $apply() inside $apply() will throw an error.
  • You need to call it only if you code is not written using methods from AngularJS library. Ex: if you are binding a double-click event on an element using jQuery, then you have to wrap your code in $apply().If you write any code that uses Ajax without $http, or listens for events without using Angular’s ng-* listeners, or sets a timeout without $timeout, you should wrap your code in $apply().
  • Some people don’t recommend calling $apply because they think that they are doing something wrong. That is not true. It is just Angular that doesn’t get any notification when a 3rd party library wants to update the bindings.

Sample:
Working Demo

HTML:-
Angular JS binding Textbox

JavaScript :-
Databinding example code
JavaScript Code

 Output:-

Try to execute the code and observe how output varies, it is changing from "Title" ->"UFT"->"User Friendly Techy Help"

Scope's $apply() method transitions through the following stages:
1.The expression is executed using the $eval() method.
2.Any exceptions from the execution of the expression are forwarded to the $exceptionHandler service.
3.The watch listeners are fired immediately after the expression was executed using the $digest() method.

If you want to create your own $watch, checkout this tutorial.

We hope that you understand how data-binding works in Angular. Don’t assume that dirty-checking is slow. It is fast as lightening. It can be laggy, if you have 2k-3k $watch in your template. In ECMAScript 7, we will have Object.observe which will improve the $digest loop.

2 comments:

  1. Really nice blog post.provided a helpful information.I hope that you will post more updates like thisAngularJS 5 Online Training

    ReplyDelete