Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does the $scope and $$phase workaround always work as expected in AngularJS?

Tags:

angularjs

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?

like image 648
matsko Avatar asked Nov 30 '12 05:11

matsko


People also ask

How do you fix $Digest already in progress?

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 });

What is the use of scope apply in AngularJS?

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.


2 Answers

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.

like image 103
matys84pl Avatar answered Nov 03 '22 00:11

matys84pl


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.

like image 45
e-satis Avatar answered Nov 02 '22 23:11

e-satis