I don't understand how to use $scope.$watch
and $scope.$apply
. The official documentation isn't helpful.
What I don't understand specifically:
- Are they connected to the DOM?
- How can I update DOM changes to the model?
- What is the connection point between them?
I tried this tutorial, but it takes the understanding of $watch
and $apply
for granted.
What do $apply
and $watch
do, and how do I use them appropriately?
You need to be aware about how AngularJS works in order to understand it.
Digest cycle and $scope
First and foremost, AngularJS defines a concept of a so-called digest cycle. This cycle can be considered as a loop, during which AngularJS checks if there are any changes to all the variables watched by all the
$scope
s. So if you have$scope.myVar
defined in your controller and this variable was marked for being watched, then you are implicitly telling AngularJS to monitor the changes onmyVar
in each iteration of the loop.A natural follow-up question would be: Is everything attached to
$scope
being watched? Fortunately, no. If you would watch for changes to every object in your$scope
, then quickly a digest loop would take ages to evaluate and you would quickly run into performance issues. That is why the AngularJS team gave us two ways of declaring some$scope
variable as being watched (read below).$watch helps to listen for $scope changes
There are two ways of declaring a
$scope
variable as being watched.<span>{{myVar}}</span>
$watch
serviceAd 1) This is the most common scenario and I'm sure you've seen it before, but you didn't know that this has created a watch in the background. Yes, it had! Using AngularJS directives (such as
ng-repeat
) can also create implicit watches.Ad 2) 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 is helpful. For instance, if you want to run some code each time 'myVar' changes, you could do the following:$apply enables to integrate changes with the digest cycle
You can think of the
$apply
function as of an integration mechanism. You see, each time you change some watched variable attached to the$scope
object directly, AngularJS will know that the change has happened. This is because AngularJS already knew to monitor those changes. So if it happens in code managed by the framework, the digest cycle will carry on.However, sometimes you want to change some value outside of the AngularJS world and see the changes propagate normally. Consider this - you have a
$scope.myVar
value which will be modified within a jQuery's$.ajax()
handler. This will happen at some point in future. AngularJS can't wait for this to happen, since it hasn't been instructed to wait on jQuery.To tackle this,
$apply
has been introduced. It lets you start the digestion cycle explicitly. However, you should only use this to migrate some data to AngularJS (integration with other frameworks), but never use this method combined with regular AngularJS code, as AngularJS will throw an error then.How is all of this related to the DOM?
Well, you should really follow the tutorial again, now that you know all this. The digest cycle will make sure that the UI and the JavaScript code stay synchronised, by evaluating every watcher attached to all
$scope
s as long as nothing changes. If no more changes happen in the digest loop, then it's considered to be finished.You can attach objects to the
$scope
object either explicitly in the Controller, or by declaring them in{{expression}}
form directly in the view.I hope that helps to clarify some basic knowledge about all this.
Further readings:
This blog has been covered all that creating examples and understandable explanations.
The AngularJS
$scope
functions$watch(), $digest()
and$apply()
are some of the central functions in AngularJS. Understanding$watch()
,$digest()
and$apply()
is essential in order to understand AngularJS.When you create a data binding from somewhere in your view to a variable on the $scope object, AngularJS creates a "watch" internally. A watch means that AngularJS watches changes in the variable on the
$scope object
. The framework is "watching" the variable. Watches are created using the$scope.$watch()
function which I will cover later in this text.At key points in your application AngularJS calls the
$scope.$digest()
function. This function iterates through all watches and checks if any of the watched variables have changed. If a watched variable has changed, a corresponding listener function is called. The listener function does whatever work it needs to do, for instance changing an HTML text to reflect the new value of the watched variable. Thus, the$digest()
function is what triggers the data binding to update.Most of the time AngularJS will call the $scope.$watch() and
$scope.$digest()
functions for you, but in some situations you may have to call them yourself. Therefore it is really good to know how they work.The
$scope.$apply()
function is used to execute some code, and then call$scope.$digest()
after that, so all watches are checked and the corresponding watch listener functions are called. The$apply()
function is useful when integrating AngularJS with other code.I will get into more detail about the
$watch(), $digest()
and$apply()
functions in the remainder of this text.$watch()
The
$scope.watch()
function creates a watch of some variable. When you register a watch you pass two functions as parameters to the$watch()
function:Here is an example:
The first function is the value function and the second function is the listener function.
The value function should return the value which is being watched. AngularJS can then check the value returned against the value the watch function returned the last time. That way AngularJS can determine if the value has changed. Here is an example:
This example valule function returns the
$scope
variablescope.data.myVar
. If the value of this variable changes, a different value will be returned, and AngularJS will call the listener function.Notice how the value function takes the scope as parameter (without the $ in the name). Via this parameter the value function can access the
$scope
and its variables. The value function can also watch global variables instead if you need that, but most often you will watch a$scope
variable.The listener function should do whatever it needs to do if the value has changed. Perhaps you need to change the content of another variable, or set the content of an HTML element or something. Here is an example:
This example sets the inner HTML of an HTML element to the new value of the variable, embedded in the b element which makes the value bold. Of course you could have done this using the code
{{ data.myVar }
, but this is just an example of what you can do inside the listener function.$digest()
The
$scope.$digest()
function iterates through all the watches in the$scope object
, and its child $scope objects (if it has any). When$digest()
iterates over the watches, it calls the value function for each watch. If the value returned by the value function is different than the value it returned the last time it was called, the listener function for that watch is called.The
$digest()
function is called whenever AngularJS thinks it is necessary. For instance, after a button click handler has been executed, or after anAJAX
call returns (after the done() / fail() callback function has been executed).You may encounter some corner cases where AngularJS does not call the
$digest()
function for you. You will usually detect that by noticing that the data bindings do not update the displayed values. In that case, call$scope.$digest()
and it should work. Or, you can perhaps use$scope.$apply()
instead which I will explain in the next section.$apply()
The
$scope.$apply()
function takes a function as parameter which is executed, and after that$scope.$digest()
is called internally. That makes it easier for you to make sure that all watches are checked, and thus all data bindings refreshed. Here is an$apply()
example:The function passed to the
$apply()
function as parameter will change the value of$scope.data.myVar
. When the function exits AngularJS will call the$scope.$digest()
function so all watches are checked for changes in the watched values.Example
To illustrate how
$watch()
,$digest(
) and$apply()
works, look at this example:his example binds the
$scope.data.time
variable to an interpolation directive which merges the variable value into the HTML page. This binding creates a watch internally on the$scope.data.time variable
.The example also contains two buttons. The first button has an
ng-click
listener attached to it. When that button is clicked the$scope.updateTime()
function is called, and after that AngularJS calls$scope.$digest()
so that data bindings are updated.The second button gets a standard JavaScript event listener attached to it from inside the controller function. When the second button is clicked that listener function is executed. As you can see, the listener functions for both buttons do almost the same, but when the second button's listener function is called, the data binding is not updated. That is because the
$scope.$digest()
is not called after the second button's event listener is executed. Thus, if you click the second button the time is updated in the$scope.data.time
variable, but the new time is never displayed.To fix that we can add a
$scope.$digest()
call to the last line of the button event listener, like this:Instead of calling
$digest()
inside the button listener function you could also have used the$apply()
function like this:Notice how the
$scope.$apply()
function is called from inside the button event listener, and how the update of the$scope.data.time
variable is performed inside the function passed as parameter to the$apply()
function. When the$apply()
function call finishes AngularJS calls$digest()
internally, so all data bindings are updated.AngularJS extends this events-loop, creating something called
AngularJS context
.$watch()
Every time you bind something in the UI you insert a
$watch
in a$watch
list.Here we have
$scope.user
, which is bound to the first input, and we have$scope.pass
, which is bound to the second one. Doing this we add two$watch
es to the$watch
list.When our template is loaded, AKA in the linking phase, the compiler will look for every directive and creates all the
$watch
es that are needed.AngularJS provides
$watch
,$watchcollection
and$watch(true)
. Below is a neat diagram explaining all the three taken from watchers in depth.http://jsfiddle.net/2Lyn0Lkb/
$digest
loopWhen the browser receives an event that can be managed by the AngularJS context the
$digest
loop will be fired. This loop is made from two smaller loops. One processes the$evalAsync
queue, and the other one processes the$watch list
. The$digest
will loop through the list of$watch
that we haveHere we have only one
$watch
because ng-click doesn’t create any watches.We press the button.
$digest
loop will run and will ask every $watch for changes.$watch
which was watching for changes in $scope.name reports a change, it will force another$digest
loop.$digest
loop. That means that every time we write a letter in an input, the loop will run checking every$watch
in this page.$apply()
If you call
$apply
when an event is fired, it will go through the angular-context, but if you don’t call it, it will run outside it. It is as easy as that.$apply
will call the$digest()
loop internally and it will iterate over all the watches to ensure the DOM is updated with the newly updated value.The
$apply()
method will trigger watchers on the entire$scope
chain whereas the$digest()
method will only trigger watchers on the current$scope
and itschildren
. When none of the higher-up$scope
objects need to know about the local changes, you can use$digest()
.I found very in-depth videos which cover
$watch
,$apply
,$digest
and digest cycles in:AngularJS - Understanding Watcher, $watch, $watchGroup, $watchCollection, ng-change
AngularJS - Understanding digest cycle (digest phase or digest process or digest loop)
AngularJS Tutorial - Understanding $apply and $digest (in depth)
Following are a couple of slides used in those videos to explain the concepts (just in case, if the above links are removed/not working).
In the above image, "$scope.c" is not being watched as it is not used in any of the data bindings (in markup). The other two (
$scope.a
and$scope.b
) will be watched.From the above image: Based on the respective browser event, AngularJS captures the event, performs digest cycle (goes through all the watches for changes), execute watch functions and update the DOM. If not browser events, the digest cycle can be manually triggered using
$apply
or$digest
.More about
$apply
and$digest
:Just finish reading ALL the above, boring and sleepy (sorry but is true). Very technical, in-depth, detailed, and dry. Why am I writing? Because AngularJS is massive, lots of inter-connected concepts can turn anyone going nuts. I often asked myself, am I not smart enough to understand them? No! It's because so few can explain the tech in a for-dummie language w/o all the terminologies! Okay, let me try:
1) They are all event-driven things. (I hear the laugh, but read on)
2) $watch is "on-click".
3) $digest is the boss who checks around tirelessly, bla-bla-bla but a good boss.
4) $apply gives you the way when you want to do it manually, like a fail-proof (in case on-click doesn't kick in, you force it to run.)
Now, let's make it visual. Picture this to make it even more easy to grab the idea:
In a restaurant,
- WAITERS are supposed to take orders from customers, this is
- MANAGER running around to make sure all waiters are awake, responsive to any sign of changes from customers. This is
$digest()
- OWNER has the ultimate power to drive everyone upon request, this is
$apply()
In AngularJS, we update our models, and our views/templates update the DOM "automatically" (via built-in or custom directives).
$apply and $watch, both being Scope methods, are not related to the DOM.
The Concepts page (section "Runtime") has a pretty good explanation of the $digest loop, $apply, the $evalAsync queue and the $watch list. Here's the picture that accompanies the text:
Whatever code has access to a scope – normally controllers and directives (their link functions and/or their controllers) – can set up a "watchExpression" that AngularJS will evaluate against that scope. This evaluation happens whenever AngularJS enters its $digest loop (in particular, the "$watch list" loop). You can watch individual scope properties, you can define a function to watch two properties together, you can watch the length of an array, etc.
When things happen "inside AngularJS" – e.g., you type into a textbox that has AngularJS two-way databinding enabled (i.e., uses ng-model), an $http callback fires, etc. – $apply has already been called, so we're inside the "AngularJS" rectangle in the figure above. All watchExpressions will be evaluated (possibly more than once – until no further changes are detected).
When things happen "outside AngularJS" – e.g., you used bind() in a directive and then that event fires, resulting in your callback being called, or some jQuery registered callback fires – we're still in the "Native" rectangle. If the callback code modifies anything that any $watch is watching, call $apply to get into the AngularJS rectangle, causing the $digest loop to run, and hence AngularJS will notice the change and do its magic.