Thursday, 10 September 2015

Concepts and use of $watch, $apply and $digest in Angular JS




In the last topic you had the first taste of what Angular is so famous for. If Angular was new to you, you would have really appreciated it. Believe it or not, this is pretty much Angular is all about. Well, surely in a big and better way !!!


All the work in Angular is very much based on this 2-way binding approach. So if you really want to grab the gist of Angular, its very important to grab the root of how actually it happens.

Well, this tutorial explains it all. However, I have seen that most people prefer to read it or teach it, once they have read or taught much in Angular. I don't have conflicts with them, however I would personally like to talk about this at the start, so that going ahead you have some idea of what's Angular doing behind the scenes.

So here we start....

There are 3, three really cool concepts in Angular:

  1. $digest()
  2. $watch()
  3. $apply()

$digest:

Angular defines a concept of a digest cycle. This cycle can be considered as a loop, during which Angular checks if there are any changes to all the variables watched by all the $scopes. So if you have $scope.variable defined in your controller and this variable was marked for being watched, then you are implicitly telling Angular to monitor the changes on this variable in each iteration of the loop.
Also its really important to know that all watches are considered in every digest cycle, whether there values has changed or not. If there values have changed, they are updated else they are kept unchanged.

Next natural question would be: Is everything attached to $scope being watched always? Fortunately, no. If you would watch for changes to every object in your $scope, then digest loop would take ages to end and you would run into performance issues.

So what is being watched??

$watch:


There are two ways of declaring a $scope variable as being watched.

By using it in your template via expression: 

As you already saw in last tutorial, variables kept inside {{ }} or with ng-bind, are being watched by default. This is the most common scenario, every time you use {{ }} you create a new watch. You will see ahead there are many places where you will be using them. So have one clear thing in mind,
one {{ }} means one watch.

By adding it manually via $watch service

This is how you create your own watches. $watch service helps you to run some code when some value attached to the $scope has changed. It is rarely used, but sometimes its really helpful. For instance, if you want to run some code each time 'thisVariable' changes, you could do the following:

function MyController($scope) {

   $scope.thisVariable = 1;

   $scope.$watch('thisVariable', function() {
       alert('Hey, thisVariable has changed!');
   });
}

$watch takes a variable and a listener function or callback function. It watches for the previous value of variable and if the value has changed, it executes the listener function.

So, each time thisVariable changes, you get a alert pop-up saying  Hey, thisVariable has changed! without any further lines of code.

$apply:


$apply enables to integrate changes with the digest cycle. You can think of the $apply function as of an integration mechanism. Each time you change some watched variable attached to the $scope object, Angular will know that the change has happened. This is because Angular already was watching these variable. So if it happens in code managed by the Angular framework, the digest cycle will occur automatically. However, sometimes you want to change some value outside of the Angular world and see the changes propagate normally. 

Consider, you have a $scope.jVariable value which will be modified within a jQuery's $.ajax() handler. This will happen at some point in future. Angular can't wait for this to happen, since it hasn't been coded to watch inside jQuery. To handle this, $apply has been introduced. It lets you to start the digestion cycle explicitly. However, you should only use this to apply a change that has occurred outside Angular, and should never use this method combined with regular Angular code, as Angular will throw an error then as Angular's $apply() would already be working.

Dirty-checking in Angular:

Few Interesting cases:


The basic implementation is covered above but we're still far from done. For instance, there's a fairly typical scenario we didn't consider: The listener functions of $watch() themselves may also change properties on the scope. If this happens, and there's another watcher looking at the property that just changed, it might not notice the change during the same digest pass. We need to change the digest so that it keeps iterating over all watches until the watched values stop changing. 

Not to worry, $digest runs all watches at least once. If, on the first pass, any of the watched values has changed, the pass is marked dirty, and all watches are run for a second time. This goes on until there's a full pass where none of the watched values has changed and the situation is deemed stable.

It is also possible that two Angular watches are depending on each other and each changing when the other gets changed. In such a case, the stable condition would never arrive. 

What Angular need to do and it does is keep running the digest for some acceptable amount of iterations. If the scope is still changing after those iterations we have to throw our hands up and declare it's probably never going to stabilize. At that point we might as well throw an exception, since whatever the state of the scope is it's unlikely to be what the user was aiming for.

This maximum amount of iterations is called the TTL (short for Time To Live). By default it is set to 10. The number may seem small, but bear in mind this is a performance sensitive area since digests happen often and each digest runs all watch functions. So Angular can't keep waiting endlessly for watches to become stable. Also it's also unlikely that a user will have more than 10 watches chained back-to-back. 

Value-based dirty checking


By default Angular compares the old value to the new with the strict equality operator = = =. This is fine in most cases, as it detects changes to all primitives (numbers, strings, etc.) and also detects when an object or an array changes to a new one. But there is also another way Angular can detect changes, and that's detecting when something inside an object or an array changes. That is, you can watch for changes in value, not just in reference.

This kind of dirty-checking is activated by providing a third, optional boolean flag to the $watch function. When the flag is true, value-based checking is used.  All we do is add the flag to the watcher. When a user calls $watch without a third argument, third parameter will be undefined, which becomes false in the watcher object.

NaNs


In JavaScript, NaN (Not-a-Number) is not equal to itself. This may sound strange, and that's because it is. If we don't explicitly handle NaN in our dirty-checking function, a watch that has NaN as a value will always be dirty because it will never be equal to its previous value. For value-based dirty-checking this case is already handled for us by the Lo-Dash isEqual function. For reference-based checking we need to handle it ourselves.

So that's pretty enough for you to go ahead. However, nothing is really enough in Web development. To fill your appetite you can look into more articles on this topic over the web. Happy learning..

No comments:

Post a Comment

Your valuable comments on this post are welcomed. Think something is missing, something is inappropriate or want to share something???
Please feel free to comment..