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?
There are a few ways to deal with this. The easiest way to deal with this is to use the built in $timeout, and a second way is if you are using underscore or lodash (and you should be), call the following: $timeout(function(){ //any code in here will automatically have an apply run afterwards });
Scopes provide APIs ($apply) to propagate any model changes through the system into the view from outside of the "AngularJS realm" (controllers, services, AngularJS event handlers). Scopes can be nested to limit access to the properties of application components while providing access to shared model properties.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With