Saturday 26 March 2016

AngularJS: $watch, $digest and $apply

While browsing reddit, I read an article I felt over-complicated an explanation of $watch, $digest and $apply, and perhaps gave people a little bit of the wrong idea about what it is.

What is a $watch? 


Let's talk about this first. $watch is arguably the most important internal feature of Angular. $watches can be used to watch any value, and trigger a function call when that value changes. A $watch can be set up from any $scope by calling $scope.$watch() as shown below.

Setting up a $watch


There are two ways to set up a watch, by expression, or by function. Technically, they both do the same thing. If you pass an expression (a string), it will be evaluated against $scope and converted to a function to be watched. If you pass a function, it just watches that function, with no conversion necessary.


By Expression

The following will watch 'foo'. Which is an expression evaluated against $scope.
  1. //$scope.$watch(<function/expression>, <handler>); $scope.$watch('foo', function(newVal, oldVal) { console.log(newVal, oldVal); });


By Function

To set up a $watch by function, you can do the following, which is technically the same as what is shown above:
  1. $scope.$watch(function() { return $scope.foo; }, function(newVal, oldVal) { console.log(newVal, oldVal); });


Facts about $watch:

  • A watcher can evaluate any value.
  • A watcher's handler can execute anything when aforementioned value has changed.
  • All watchers are evaluated when $digest() is called.
  • If the first argument of a $watch is a string, it is $eval'ed into a function prior to registration. It's functionally equivalent to passing a function as the first argument, just with an extra step internally.


What is $digest?


At it's core, the important thing to know about $digest is that it loops through all watchers on the scope it was called on and it's child scopes. and evaluates them to see if they've changed, executing their handlers if they have. That's the important part you need to know.

How to call $digest:

  1. $scope.$digest();


What is $apply?


Simply put, it's a wrapper around $rootScope.$digest that evaluates any expression passed to it prior to calling $digest(). That's it.  So, if you're calling it by itself without passing an argument to it, you may as well just call $digest().

How to call $apply:


  1. $scope.$apply('foo = "test"'); //or $scope.$apply(function(scope) { scope.foo = 'test'; }); //or $scope.$apply(function(){ $scope.foo = 'test'; });

So when does a $digest/$apply happen?


At key moments defined by the framework. The Angular framework has built in calls to $digest and $apply to it's services and directives as well as some internal calls. But basically it's like this, things like $timeout perform a setTimeout, then call $apply or $digest (it actual does a little more than that, but that's the basic idea). $http.get(), same deal, makes an AJAX call, returns it, then queues up a $digest.  Then there are directives, like inputs with ngModel for example. Updates to the input will also trigger a $digest. You get the idea.

How do $watch, $digest, and $apply relate to updating my view?


  • The directive registers a $watch that looks for a change in the model on the $scope. The handler will update the DOM element's value.
  • The directive registers an event handler of some sort in the DOM that will get a value from the DOM and apply it to the model in $scope. It will also call $apply or $digest.
  • When you update the model in the scope via some in-framework call... $http.get() for example, it kicks off a $digest after it completes.
  • The $digest checks the $watch the directive registered, sees the change and fires the handler associated to it, updating the DOM element.

Why does Angular work this way?


Since Angular wanted to use plain-old-JavaScript-objects (*POJSO© Ben Lesh 2013 all rights reserved!! Brought to you by Carl's Jr), a digest was the only real choice. Why? Well you can observe setter and getters on known properties real-time, but there's really no way to build an event into adding new values to or removing values from an object or array, particularly when it's done by an indexer, ala hash table or array index. So a digest becomes necessary. How do I know this? Because I tried to write my own framework and quickly found myself writing a half-assed version of Angular. The only other option that I know of would be to completely wrap the model observation up in a construct with set() and get() functions... think Knockout... which makes the JavaScript code uglier to deal with (IMO).


Some Guidelines For Use:


  • $watch
    • DO use $watch in directives to update the DOM when a $scope value changes.
    • DON'T use $watch in a controller. It's hard to test and completely unnecessary in almost every case. Use a method on the scope to update the value(s) the watch was changing instead.
  • $digest/$apply
    • DO use $digest/$apply in directives to let Angular know you've made changes after an asynchronous call, such as a DOM event.
    • DO use $digest/$apply in services to let Angular know some asynchronous operation has returned, such as a WebSocket update, or an event from a 3rd party library like Facebook API.
    • DON'T use $digest/$apply in a controller. This will make your code harder to test, and asynchronous operations outside of the Angular framework don't belong in your controllers. They belong in services and directives.
Remember these are guidelines, not hard, fast rules. More importantly, they're my guidelines, so take them with a grain of salt.



When to use $apply vs $digest?


 $scope.$digest should rarely be used outside of some very specific instances. Such as an isolated scope in a directive that might want to only update itself. Or, in an extreme edge case if a scope object has been created for some other isolated purpose. $scope.$apply or $rootScope.$digest should be favored most of the time, as the general desired effect is to update an entire view or model. 

3 comments:

  1. Thanks to help me but you can provide full example so we can understand more...

    ReplyDelete
    Replies
    1. Thanks to give me suggestion. i will provide you full example in same page... :)

      Delete
    2. okay thanks to give replay , i am waiting for your example. :-)

      Delete

Thanks to comment our blog. i will contact you as soon as possible

Create Thumbnail in Video in Spring and ffmpeg

import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import org.jcodec.api.Fr...