Related
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 $scopes. 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 on myVar 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.
By using it in your template via the expression <span>{{myVar}}</span>
By adding it manually via the $watch service
Ad 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:
function MyController($scope) {
$scope.myVar = 1;
$scope.$watch('myVar', function() {
alert('hey, myVar has changed!');
});
$scope.buttonClicked = function() {
$scope.myVar = 2; // This will trigger $watch expression to kick in
};
}
$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 $scopes 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.
Further readings:
Make Your Own AngularJS, Part 1: Scopes And Digest
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.
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.
User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />
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 $watches 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 $watches that are needed.
AngularJS provides $watch, $watchcollection and $watch(true). Below is a neat diagram explaining all the three taken from watchers in depth.
angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
$scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];
$scope.$watch("users", function() {
console.log("**** reference checkers $watch ****")
});
$scope.$watchCollection("users", function() {
console.log("**** Collection checkers $watchCollection ****")
});
$scope.$watch("users", function() {
console.log("**** equality checkers with $watch(true) ****")
}, true);
$timeout(function(){
console.log("Triggers All ")
$scope.users = [];
$scope.$digest();
console.log("Triggers $watchCollection and $watch(true)")
$scope.users.push({ name: 'Thalaivar'});
$scope.$digest();
console.log("Triggers $watch(true)")
$scope.users[0].name = 'Superstar';
$scope.$digest();
});
}
http://jsfiddle.net/2Lyn0Lkb/
$digest loop
When 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 have
app.controller('MainCtrl', function() {
$scope.name = "vinoth";
$scope.changeFoo = function() {
$scope.name = "Thalaivar";
}
});
{{ name }}
<button ng-click="changeFoo()">Change the name</button>
Here we have only one $watch because ng-click doesn’t create any watches.
We press the button.
The browser receives an event which will enter the AngularJS context
The $digest loop will run and will ask every $watch for changes.
Since the $watch which was watching for changes in $scope.name
reports a change, it will force another $digest loop.
The new loop reports nothing.
The browser gets the control back and it will update the DOM
reflecting the new value of $scope.name
The important thing here is that EVERY event that enters the AngularJS context will run a $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 its children. 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:
There are $watchGroup and $watchCollection as well. Specifically, $watchGroup is really helpful if you want to call a function to update an object which has multiple properties in a view that is not dom object, for e.g. another view in canvas, WebGL or server request.
Here, the documentation link.
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)
If you don't know what event-driven is Then
think you place a button
on the page, hook it up w/ a function using "on-click", waiting for
users to click on it to trigger the actions you plant inside the
function. Or think of "trigger" of SQL Server / Oracle.
2) $watch is "on-click".
What's special about is it takes 2 functions as parameters, first one
gives the value from the event, second one takes the value into
consideration...
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
$watch(
function(){return orders;},
function(){Kitchen make it;}
);
- 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()
I am creating reusable UI components with AngularJS directives. I would like to have a controller that contains my business logic with the nested components (directives). I want the directives to be able to manipulate a single property on the controller scope. The directives need to have an isolate scope because I might use the same directive more than once, and each instance needs to be bound to a particular controller scope property.
So far, the only way I can apply changes back to the controller's scope is to call scope.$apply() from the directive. But this breaks when I'm inside of an ng-click callback because of rootScope:inprog (scope operation in progress) errors.
So my question: What is the best way to make my controller aware when a child directive has updated a value on the controller's scope?
I've considered having a function on the controller that the directive could call to make an update, but that seems heavy to me.
Here is my code that breaks on an ng-click callback. Keep in mind that I don't just want to solve the ng-click issue. I want the best overall solution to apply reusable directives to modify a parent scope/model.
html
<div ng-controller="myCtrl">
<my-directive value="val1"></my-directive>
</div>
controller
...
.controller('myCtrl', ['$scope', function ($scope) {
$scope.val1 = 'something';
}});
directive
...
.directive('myDirective', [function () {
return {
link: function(scope) {
scope.buttonClick = function () {
var val = 'new value';
scope.value = val;
scope.$apply();
};
},
scope: {
value: '='
},
template: '<button ng-click="buttonClick()"></button>'
};
}]);
The purpose of two-way data binding in directives is exactly what you're asking about -- to "[allow] directives to modify a parent scope/model."
First, double-check that you have set up two-way data binding correctly on the directive attribute which exposes the variable you want to share between scopes. In the controller, you can use $watch to detect updates if you need to do something when the value changes. In addition, you have the option of adding an event-handler attribute to the directive. This allows the directive to call a function when something happens. Here's an example:
<div ng-controller="myCtrl">
<my-directive value="val1" on-val-change="myFunc"> <!-- Added on-change binding -->
<button ng-click="buttonClick()"></button>
</my-directive>
</div>
I think your question about $scope.apply is a red herring. I'm not sure what problem it was solving for you as you evolved this demo and question, but that's not what it's for, and FWIW your example works for me without it.
You're not supposed to have to worry about this issue ("make controller aware ... that [something] modified a value on a scope"); Angular's data binding takes care of that automatically.
It is a little complicated here because with the directive, there are multiple scopes to worry about. The outer scope belongs to the <div ng-controller=myCtrl>, and that scope has a .val property, and there's an inner scope created by the <my-directive> which also has a .val property, and the buttonClick handler inside myDirective modifies the inner one. But you declared myDirective's scope with value: '=' which sets up bidirectional syncing of that property value between the inner and outer scope.
So it should work automatically, and in the plunker I created from your question code, it does work automatically.
So where does scope.$apply come in? It's explicitly for triggering a digest cycle when Angular doesn't know it needs to. (And if you use it when Angular did know it needed a digest cycle already, you get a nested digest cycle and the "inprog" error you noticed.) Here's the doc link, from which I quote "$apply() is used to execute an expression in angular from outside of the angular framework". You need to use it, for example, when responding to an event handler set up with non-Angular methods -- direct DOM event bindings, jQuery, socket.io, etc. If you're using these mechanisms in an Angular app it's often best to wrap them in a directive or service that handles the Angular-to-non-Angular interface so the rest of your app doesn't have to worry about it.
(scope.$apply is actually a wrapper around scope.$digest that also manages exception handling. This isn't very clear from the docs. I find it easier to understand the name/behavior of $digest, and then consider $apply to be "the friendlier version of $digest that I'm actually supposed to use".)
One final note on $apply; it takes a function callback argument and you're supposed to do the work inside this callback. If you do some work and then call $apply with no arguments afterwards, it works, but at that point it's the same as $digest. So if you did need to use $apply here, it should look more like:
scope.buttonClick = function() {
scope.$apply(function() {
scope.value = newValue;
});
});
I'm reading Build Your Own AngularJS and have a decent understanding of how $scopes, $watch and $digest work. I understand how it works when you add your own $watches and and call your own $digests. However, I'm confused about what exactly is happening by default.
What gets added to $scope.$$watchers by default? Everything you put on $scope? Everything you assign an ng-model to? Both? Something else?
And when exactly do $digests get triggered by default? Input field changes? Input fields with ng-models? Other?
Some of the common directives that use $watch / $watchCollection / $watchGroup internally:
ng-model
ng-bind / {{ }}
ng-show & ng-hide
ng-class
ng-repeat
ng-if
ng-switch
ng-include
Note that the only one that sets up a two-way binding is ng-model (scope -> view & view -> scope).
The others set up a one-way binding (scope -> view).
Simply exposing something on for example a controller´s $scope will not add a watcher.
For example, the following will not result in a watcher being added:
angular.module('myApp', []).controller('Controller', function MyCtrl($scope) {
$scope.value = 1;
});
Together with:
<body ng-app="myApp" ng-controller="Controller">
</body>
But if you replace the HTML with the following one watcher will be added:
<body ng-app="myApp" ng-controller="Controller">
<div>{{value}}</div>
</body>
Some common scenarios when the digest cycle is triggered:
When ng-click is evaluated
When ng-model changes (for example when typing in an input)
By the $http service
In $timeout and $interval
Note that there is one big difference between $apply and $digest:
Calling scope.$digest() will execute the watchers only on that scope and its children.
Calling scope.$apply() will trigger $digest on the $rootScope, which means all the scopes will be traversed and all watchers executed.
$apply also accepts an expression as an argument. This expression will be evaluated inside a try-catch statement and any exception will be passed on to the $exceptionHandler service.
$digest does not accept any arguments.
Usually you only call $digest instead of $apply when you are chasing micro optimizations and really know what you are doing.
It's my understanding that any two-way bindings get a $watch in their scope, however, if it is added by angular internals, you don't get that hook, because, for example, ngModelController has the callback, so you can't use that callback, I think what is on scope does not get a watch unless it is bound to the view.
$digest is not used on model binding from what I found in the source code - but I found plenty of uses of $apply. In fact I found very few uses of $digest in the angular code at all. I didn't look in every file, but I did find it used in the expression parser here. Which I find interesting, but to answer your question, $digest in not called often, and $apply is called on only a few occasions with model binding, most notably on $commitViewValue() in the ngModelController. It also calls apply when an input is "touched"(ng-touched). I also found that $evalAsync calls $digest as well.
What I found for certain on apply/digest:
//apply method in rootScope
$apply: function(expr) {
try {
beginPhase('$apply');
return this.$eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
clearPhase();
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
}
}
As you can see, apply is really just a safe wrap around $digest, so one might argue to use $apply over digest. According to the source $digest does some crazy while-loop $scope traversal.
TLDR;
The notes from the angular team on digest:
Processes all of the {#link ng.$rootScope.Scope#$watch watchers} of
the current scope and
its children. Because a {#link ng.$rootScope.Scope#$watch watcher}'s listener can change
the model, the $digest() keeps calling the {#link ng.$rootScope.Scope#$watch watchers}
until no more listeners are firing. This means that it is possible to get into an infinite
loop. This function will throw 'Maximum iteration limit exceeded.' if the number of
iterations exceeds 10.
Usually, you don't call $digest() directly in
{#link ng.directive:ngController controllers} or in
{#link ng.$compileProvider#directive directives}.
Instead, you should call {#link ng.$rootScope.Scope#$apply $apply()} (typically from within
a {#link ng.$compileProvider#directive directive}), which will force a $digest().
If you want to be notified whenever $digest() is called,
you can register a watchExpression function with
{#link ng.$rootScope.Scope#$watch $watch()} with no listener.
I am reviewing someone else's code and I see that they have $scope.$apply in their directive.
The scenario is that we got some event from the DOM and we want to change the scope.
From my experience, directives should call apply. It causes some weird side effects.
One of them is in the directive's test. All my tests have the same pattern
$compile( "<the html>" )(scope);
scope.$digest(); --> will error if directive calls apply
Should directives call apply?
What is the recommended solution for apply changes on scope when you get an event from DOM which is not wrapped in angular?
I would say that calling $scope.$apply or $scope.$digest is usually (although not always) a bad idea.
For your example, registering DOM event can go through angular using ng-click, ng-keydown, etc, which will conceal the need to call $apply or $digest.
The reason it is even needed, is obviously because there is some code executed "outside" angular, meaning, outside of the angular ecosystem and so basically angular doesn't "know" an event (or any other data related thing) has happened.
So to sum up, there should be a (very) good reason to call $apply or $digest.
How else?
Well, you could encapsulate these event capturing inside your own directive (although most if not all of them are covered on angular). These is exactly what angular itself does and will result $apply or $digest only when actually needed by the event itself.
/EDIT/
For instance, a simplified version of angular's ng-click can be translated into your own directive:
app.directive('myClick', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var clickHandler = $parse(attr.myClick);
element.on('click', function(event) {
// Do some of your own logic if needed.
scope.$apply(function() {
// Calling the event handler.
clickHandler(scope, {$event: event});
});
});
}
}
}]);
By encapsulating this event handler, it can be reused (in this a form of a directive) and because being part of angular's world, any other logic using this directive, doesn't have to worry about $apply or $digest. It also means it can be used declaratively now (rather then operatively) which is what angular aspires anyway.
One thing to notice, this directive doesn't isolate its scope and doesn't introduce any other new variables on the scope (the event handler is being parsed on the linking function). This is important because it means there are is no overhead side effects on the parent scope (the scope that needs to "know" about this event - which is basically the main scope), since the directive's scope is inherited.
P.S You can also consider overriding directives or decorating other services on angular.
Well... if your directive wrapps some native events or anything outside of the Angular scope, you dont have much more options than calling "$apply()". From my experience this will only cause an error if this function is called from both, WITHIN the angular scope and from outside (e.g. ng-click as well as window-click event or something). If this is a case, you can still use the $timeout-Service. It's not the nicest solution, but from what I`ve heard its even the suggested one from the angular team.
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 $scopes. 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 on myVar 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.
By using it in your template via the expression <span>{{myVar}}</span>
By adding it manually via the $watch service
Ad 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:
function MyController($scope) {
$scope.myVar = 1;
$scope.$watch('myVar', function() {
alert('hey, myVar has changed!');
});
$scope.buttonClicked = function() {
$scope.myVar = 2; // This will trigger $watch expression to kick in
};
}
$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 $scopes 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.
Further readings:
Make Your Own AngularJS, Part 1: Scopes And Digest
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.
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.
User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />
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 $watches 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 $watches that are needed.
AngularJS provides $watch, $watchcollection and $watch(true). Below is a neat diagram explaining all the three taken from watchers in depth.
angular.module('MY_APP', []).controller('MyCtrl', MyCtrl)
function MyCtrl($scope,$timeout) {
$scope.users = [{"name": "vinoth"},{"name":"yusuf"},{"name":"rajini"}];
$scope.$watch("users", function() {
console.log("**** reference checkers $watch ****")
});
$scope.$watchCollection("users", function() {
console.log("**** Collection checkers $watchCollection ****")
});
$scope.$watch("users", function() {
console.log("**** equality checkers with $watch(true) ****")
}, true);
$timeout(function(){
console.log("Triggers All ")
$scope.users = [];
$scope.$digest();
console.log("Triggers $watchCollection and $watch(true)")
$scope.users.push({ name: 'Thalaivar'});
$scope.$digest();
console.log("Triggers $watch(true)")
$scope.users[0].name = 'Superstar';
$scope.$digest();
});
}
http://jsfiddle.net/2Lyn0Lkb/
$digest loop
When 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 have
app.controller('MainCtrl', function() {
$scope.name = "vinoth";
$scope.changeFoo = function() {
$scope.name = "Thalaivar";
}
});
{{ name }}
<button ng-click="changeFoo()">Change the name</button>
Here we have only one $watch because ng-click doesn’t create any watches.
We press the button.
The browser receives an event which will enter the AngularJS context
The $digest loop will run and will ask every $watch for changes.
Since the $watch which was watching for changes in $scope.name
reports a change, it will force another $digest loop.
The new loop reports nothing.
The browser gets the control back and it will update the DOM
reflecting the new value of $scope.name
The important thing here is that EVERY event that enters the AngularJS context will run a $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 its children. 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:
There are $watchGroup and $watchCollection as well. Specifically, $watchGroup is really helpful if you want to call a function to update an object which has multiple properties in a view that is not dom object, for e.g. another view in canvas, WebGL or server request.
Here, the documentation link.
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)
If you don't know what event-driven is Then
think you place a button
on the page, hook it up w/ a function using "on-click", waiting for
users to click on it to trigger the actions you plant inside the
function. Or think of "trigger" of SQL Server / Oracle.
2) $watch is "on-click".
What's special about is it takes 2 functions as parameters, first one
gives the value from the event, second one takes the value into
consideration...
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
$watch(
function(){return orders;},
function(){Kitchen make it;}
);
- 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()