42

I found this code snippet which is part of a angular directive someone wrote for bootstrap modal.

//Update the visible value when the dialog is closed                                                                                                                                                                                                            
                //through UI actions (Ok, cancel, etc.)                                                                                                                                                                                                                         
                element.bind("hide.bs.modal", function () {                                                                                                                                                                                                                     
                    scope.modalVisible = false;                                                                                                                                                                                                                                 
                    if (!scope.$$phase && !scope.$root.$$phase)                                                                                                                                                                                                                 
                        scope.$apply();                                                                                                                                                                                                                                         
                });  

I understood that this part is for the latter half of two way binding we bind to hide.bs.modal event and update modal when UI changes.

I just wanted to know why is the person checking $$phase for scope and rootScope before calling apply ?

Can't we straightaway call apply ?

What is $$phase here?

I tried searching a lot, couldn't find any good explanation.

EDIT:

I found where I saw the example: Simple Angular Directive for Bootstrap Modal

Community
  • 1
  • 1
Amogh Talpallikar
  • 12,084
  • 13
  • 79
  • 135

5 Answers5

48

$$phase is a flag set while angular is in a $digest cycle.

Sometimes (in rare cases), you want to check $$phase on the scope before doing an $apply. An error occurs if you try to $apply during a $digest:

Error: $apply already in progress

Davin Tryon
  • 66,517
  • 15
  • 143
  • 132
  • 13
    While your answer is technically correct, I disagree: IMHO no, you don't want to check for `$$phase` *often*. If you do, it proves a lack of understanding how to write an AngularJS app correctly. Sorry to say that, but if you do AngularJS the right way, there is rarely a case where you need to rely on this internal (!) variable. – Golo Roden Nov 28 '13 at 10:17
  • 1
    Ohkay! this thing :" $digest already in progress when calling $scope.$apply() " – Amogh Talpallikar Nov 28 '13 at 10:18
  • @AmoghTalpallikar Your question isn't specific enough to handle your specific circumstance. In theory, the `$$phase` is a guard against this error. Maybe you can provide an example with more detail? Or another question about the specifics? :) – Davin Tryon Nov 28 '13 at 10:33
  • 3
    There is only one case that I can think of where you want to look at `$$phase`, and that is when you are implementing a "safe apply", which will call the function directly if `$apply` is already in progress. The only time you would use "safe apply" is when you have a function that gets called from within angular and from outside angular... but this can usually be avoided by wrapping one call in `$apply` and calling it from outside, but avoiding it from inside. I've only seen one condition ever where "safe apply" looked like the best approach. – Brian Genisio Nov 28 '13 at 10:36
  • @BrianGenisio Can I quote you in the answer? – Davin Tryon Nov 28 '13 at 10:38
  • @BrianGenisio: I have added the link to the original source, have a look at it. – Amogh Talpallikar Nov 28 '13 at 11:38
  • 1
    @AmoghTalpallikar I've added an answer to explain why it is necessary in this case. – Brian Genisio Dec 02 '13 at 01:30
37

Davin is totally correct that it's a flag that angular sets during the digest cycle.

But don't use it in your code.

I recently got a chance to ask Misko (angular author) about $$phase, and he said to never use it; it's an internal implementation of the digest cycle, and it's not future safe.

To make sure your code continues to work in the future, he suggested wrapping whatever you want to "safe apply" inside of a $timeout

$timeout(function() {
  // anything you want can go here and will safely be run on the next digest.
})

This comes up a lot when you have callbacks or other things that might resolve during the digest cycle (but don't always)

Here's an example snippet from when I was dealing with one of google's libraries: (The rest of the service this was from has been left off.)

window.gapi.client.load('oauth2', 'v2', function() {
    var request = window.gapi.client.oauth2.userinfo.get();
    request.execute(function(response) {
        // This happens outside of angular land, so wrap it in a timeout 
        // with an implied apply and blammo, we're in action.
        $timeout(function() {
            if(typeof(response['error']) !== 'undefined'){
                // If the google api sent us an error, reject the promise.
                deferred.reject(response);
            }else{
                // Resolve the promise with the whole response if ok.
                deferred.resolve(response);
            }
        });
    });
});

Note that the delay argument for $timeout is optional and will default to 0 if left unset ($timeout calls $browser.defer which defaults to 0 if delay isn't set)

A little non-intuitive, but that's the answer from the guys writing Angular, so it's good enough for me!

betaorbust
  • 7,958
  • 1
  • 20
  • 15
6

In that example, the element binding will get executed from a non-angular event. In most cases, it is safe to just call $apply() without checking the phase.

If you look at the rest of the code, however, there is a $scope function called showModal(). This function calls into the non-angular code which will likely cause a "hide.bs.modal" event to fire. If the event fires via this route, then the call stack is within a $digest.

So, this event does fall within the rare case of a function that will get called from both angular-managed code AND non-angular code. Checking the $$phase in this case is necessary because you don't know how the event originated. If the $$phase is set to something, then the digest loop will finish to completion and $apply() doesn't need to be called.

This pattern is often referred to as "safe apply".

Brian Genisio
  • 47,787
  • 16
  • 124
  • 167
2

my understanding is it is good to use when digesting or applying the scope. If it is truthy it means that there is currently a $digest or $apply phase in progress. If you are getting related errors you can do $scope.$$phase || $scope.digest(); which will only digest if $scope.$$pahse is falsy.

majo
  • 55
  • 1
  • 8
0

You can use $scope.$evalAsync() method rather than using the $scope.$apply() externally with the $$phase value check as rightly suggested by @betaorbust:

I recently got a chance to ask Misko (angular author) about $$phase, and he said to never use it; it's an internal implementation of the digest cycle, and it's not future safe.

The good thing about $scope.$evalAsync() is that it gives you what you really want, get the fresh data up with the digest cycle, faster than even the $timeout can:

Up until now, my approach to deferred-$digest-invocation was to replace the $scope.$apply() call with the $timeout() service (which implicitly calls $apply() after a delay). But, yesterday, I discovered the $scope.$evalAsync() method. Both of these accomplish the same thing - they defer expression-evaluation until a later point in time. But, the $scope.$evalAsync() is likely to execute in the same tick of the JavaScript event loop.

Usage is quite simple:

$scope.$evalAsync(
    function() {
       // Call some method here OR 
       $scope.excutemyMethod(); 

       // assign some value;
       $scope.someVariable = "asd"

    }
);

The method inside the $evalAsync() is added to the Async queue so that it can be executed in the following digest cycle by internally triggering $apply which is what we really want.

Also it includes the $timeout call to handle rarest of rare scenarios watching for the async queue length and waiting the tasks in the Async Queue to be executed to put your function in the execution cycle as soon as possible. Refer Ben Nadel blog, which explains this fact.

Kailas
  • 7,350
  • 3
  • 47
  • 63