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()
Is this a bug in Angular or am I missing something:
Calling a function from the directive's template shows that that function is executed 11 times ! with a templateurl, and 22 times !! with a string template.
angular.module('testDirective', [])
.directive('myDirective', function() {
return {
scope:{},
template: '{{increment()}} {{count}}',
controller: function($scope) {
$scope.count = 0;
$scope.increment = function() {
$scope.count += 1;
};
}
};
})
HTML:
<body ng-app="testDirective">
<my-directive></my-directive>
</body>
RESULT:
22
Here is a Plunker with both template and templateUrl methods.
This is quite an issue when method calls are involved within repeaters as an example, this ends up calling the same method dramatically more time than it should.
Anybody could shed some light on this ?
{{increment()}} in your view is going to be called every time the digest cycle is called, like any bound expression would be. If you were incrementing in the controller function itself, rather than a bound function called from the view template, that would indicate that the directive was "loading" multiple times. But the nature of Angular is that any bound expression is going to be checked for changes repeatedly during the digest cycle, and anything you expect to happen only once (or even a predictable number of times) should never happen inside an expression bound to a content element. On the other hand, expressions bound to event handlers only execute when the event fires.
The model takes some time to get used to. Though the old documentation argued against it, you can use functions in content binding expressions, but they should never change state. You have no real control over when $digest() is called, and if you're thinking in the Angular way, you won't want to.
I also found this post which relates to the same issue to be useful :
Using ng-class with a function call - called multiple times
Also one-time binding can be a solution is some case as related here :
Angular lazy one-time binding for expressions
increment() is being called every time the view is being updated. On load, increment() is called, and the count goes up by 1 which causes the view to be updated and increment() to be called again.
If you look in your console, you will see that Angular is telling you that it's stuck in an infinite $digest loop.
Similar to this question, I want to set focus on the last <select> whenever it gets added. As there's a single method doing it, I need no directive and no watch and no events. My function
$scope.addNew = function() {
$scope.items.push({});
$timeout(function() {
$("select").focus();
});
};
works nicely, except when called directly from the controller function definition like
angular.module('myModule').controller('MyCtrl', function($scope, $timeout) {
$scope.items = {};
...
$scope.addNew();
}
It looks like the timeout happens before the DOM gets constructed and $("select") is empty. With a delay of some 100 ms it works again, but this is a bad hack.
Contrary to what's said in the answer to the linked question, timeout doesn't suffice.
So what's a reliable way to wait for angularjs being really done with the DOM and everything?
Update:
It probably doesn't work because of the select to be focused being embedded in directives (including ng-repeat and some own ones) That's why there initially was no DOM element to focus on.
According to the comments, I need a directive. What's unclear is how exactly to do it. I tried and failed and found out a simpler solution.
What I need
I wasn't very explicit with this, so let me clarify.
I'm working with a table where each row contains some editable fields.
In addNew, I want to set focus on the first editable field of the new row.
In my case this happens to be the very last select.
It worked except at the very beginning, when I was adding the very first row from the controller body.
Why I'm opposed to using a directive
To my limited understanding, it's completely backwards:
A directive modifies the look, behavior, or structure of a given element. But there's no element which should be modified. I tried to put a directive on everything from the select itself to the whole body.
It needs to watch something or listen to an event, but I only want to invoke a function manually.
It didn't work (for me and others as the comments to the linked question shows).
I am going to try and influence you to use a directive here, just to perform the behavior.
Here is a fiddle.
Basic premise is adding the behavioral directive to the element inside repeater:
<table>
<tr ng-repeat="item in items">
<td>{{item}}: <input type="text" auto-focus/></td>
</tr>
</table>
Then your directive would put focus on the last added element:
app.directive('autoFocus', function(){
return function link(scope, elem){
elem[0].focus();
}
});
No watchers or events needed unless I am missing something that you require.
Code that manipulates the DOM should go in a directive, but if you switch to a directive and still have reason to wait until Angular is finished updating the scope and the dom, use $scope.$evalAsync:
$scope.$evalAsync( function() {
// This will wait until Angular is done updating the scope
// Do some stuff here
//
});
The solution was very trivial: Instead of calling $scope.addNew(); directly, I put it in $scope.init invoked from <form ng-init="init()">.
According to the documentation
The only appropriate use of ngInit is for aliasing special properties of ngRepeat, as seen in the demo below. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.
this seems to be wrong (or maybe not, as ngRepeat si involved). I'm only using it to postpone the call to $scope.addNew();, where neither timeout nor posting events worked.
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()
One major thing to keep in mind when dealing with 3rd party tools as well as external'ish DOM events in AngularJS is to use the $scope.$apply() method operation to kickstart the changes. This works amazing, but sometimes the scope itself my already be grinding through a digest (which is basically what the $apply method triggers) and calling $apply when this is going on will throw an error. So to get around this, you will have to pay attention to the $scope.$$phase flag which is set to the scope whenever a digest is going on.
So now, lets say you want to change the URL and you fire off:
$scope.$apply(function() {
$location.path('/home');
});
And this works as expected, but now lets assume that the $scope is busy doing it's thing. So instead you check for the $$phase variable and assume that your changes will be picked up:
if($scope.$$phase) {
$location.path('/home');
}
else {
$scope.$apply(function() {
$location.path('/home');
});
}
This is what I've been doing (not with the code duplication obviously) and it seems to work 100% of the time. What I am concerned about is that how does AngularJS pickup the change when the scope is midway in it's digestion?
Maybe this example is not specific enough. Lets assume something bigger instead. Imagine if you have a huge webpage which plenty of bindings and lets assume that the digestion will chew through the page linearly (I'm assuming that it does something like this with respect to priority ... In this case being whatever shows up in the DOM tree first) and update the bindings on the page from top to bottom.
<div class="binding">{{ binding1 }}</div>
<div class="binding">{{ binding2 }}</div>
<div class="binding">{{ binding3 }}</div>
<div class="binding">{{ binding4 }}</div>
<div class="binding">{{ binding5 }}</div>
<div class="binding">{{ binding6 }}</div>
<div class="binding">{{ binding7 }}</div>
<div class="binding">{{ binding8 }}</div>
Lets assume that a digestion is going on and it's somewhere near the middle of the digestion queue. Now lets try and change a binding value at the top of the page somewhere.
if($scope.$$phase) {
$scope.binding1 = 'henry';
}
Now, somehow, AngularJS picks up the change and updates the binding properly. Even though the change itself can be considered to take place earlier in the queue with respect to the HTML/DOM.
My question is that how does AngularJS manage this potential race condition? I can somewhat be comfortable if binding8 updates (since it's lower down the page), but because binding1 also updates (right away without the need to call $apply again), this makes me a bit lost. Does this mean that another digestion was dispatched somewhere in between? Or is the $scope object more magical than I expect to be? I would assume that this issue has been though of before, but since finding out about $$phase and $scope in the first place was a bit tricky then I'm assuming that this also might be something that fell through the cracks.
Any ideas?
Regarding the bindings and race conditions. $digest will loop all the watchers until there is no changes. As you can observe by adding logs to watchers/binded methods it will call each binding/watcher at least twice to be sure that there is no change and all binded values are stable. Those are just dirty checks that run until every value is resolved (that have not changed within 2 loop iterations). Hope that helps.
This is explained in AngularJS docs here: http://docs.angularjs.org/api/ng.$rootScope.Scope#$digest
NOTE: This is a copy/paste of my comment as requested by matsko.
Apply will keep calling digest untill it is sure nothing has changed. So if may have a race condition at the first call, but a second one will always compensate.
You shouldn't use that workaround anyway: if the state of your $scope in uncertain on this function, you should digest it higher on your call stack where you know if you're synchroneously processed by angular, thus no need to digest, or if you're modifying the model asynchroneously so you can digest it.
On a side note, $scope.$applyis basically a $scope.$root.$digest with error handling, so if you're updating an isolated scope within a directive you can save performances by calling $digest instead of $apply:
// Somewhere in a callback
$location.path('/home');
$scope.$digest();
PS: That's for correcting e-satis answer: $digestwill also call itself while the scope is dirty.