866

I'm finding that I need to update my page to my scope manually more and more since building an application in angular.

The only way I know of to do this is to call $apply() from the scope of my controllers and directives. The problem with this is that it keeps throwing an error to the console that reads :

Error: $digest already in progress

Does anyone know how to avoid this error or achieve the same thing but in a different way?

Ricky Dam
  • 1,833
  • 4
  • 23
  • 42
Lightbulb1
  • 12,882
  • 6
  • 22
  • 23
  • 35
    It's really frustrating thing that we need use $apply more and more. – OZ_ May 20 '13 at 22:08
  • I am getting this error as well, even though I am calling $apply in a callback. I am using a third-party library to access data on their servers, so I can't take advantage of $http, nor do I want to since I would have to rewrite their library to use $http. – Trevor Nov 13 '13 at 19:15
  • 46
    use `$timeout()` – Onur Yıldırım Jan 19 '14 at 23:29
  • 6
    use $timeout(fn) + 1, It can fix the problem, !$scope.$$phase isn't the best solution. – Huei Tan Mar 26 '14 at 07:09
  • 1
    Only wrap code/call scope.$apply from **within** timeouts (not $timeout) AJAX functions (not $http) and events (not `ng-*`). Ensure, if you are calling it from within a function (that is called via timeout/ajax/events), that it's not **also** being run on load initially. – Patrick May 24 '14 at 12:22
  • I'm getting this error in a place where I'm not even calling $apply. I'm calling `element[0].focus();` – mcv Aug 25 '14 at 14:06
  • Also, try to use $digest if you can since it only runs in scope and not on the entire app. For speeds sake! :) – Mats Oct 21 '16 at 08:45
  • [angularjs and $apply](http://jimhoskins.com/2012/12/17/angularjs-and-apply.html) – tchelidze Jan 31 '18 at 10:03

28 Answers28

681

From a recent discussion with the Angular guys on this very topic: For future-proofing reasons, you should not use $$phase

When pressed for the "right" way to do it, the answer is currently

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

I recently ran into this when writing angular services to wrap the facebook, google, and twitter APIs which, to varying degrees, have callbacks handed in.

Here's an example from within a service. (For the sake of brevity, the rest of the service -- that set up variables, injected $timeout etc. -- 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
  • 5
    I've ran into this many times in my directives. Was writing one for redactor and this turned out to work perfectly. I was at a meetup with Brad Green and he said that Angular 2.0 will be huge with no digest cycle using JS's native observe ability and using a polyfill for browsers lacking that. At that point we won't need to do this anymore. :) – Michael J. Calkins Nov 10 '13 at 21:13
  • Yesterday I've seen an issue where calling selectize.refreshItems() _inside $timeout_ caused the dreaded recursive digest error. Any ideas how that could be? – iwein Mar 12 '14 at 06:02
  • Using $timeout works, bad has very bad performance. If you have a lot of diresctives it will refresh everything. – Pikachu Oct 30 '15 at 13:51
  • 3
    If you use `$timeout` rather than native `setTimeout`, why do you not use `$window` instead of the native `window`? – Lee Goddard Jan 27 '16 at 10:24
  • 2
    @LeeGee: The point of using `$timeout` in this case, is that `$timeout` ensures that the angular scope is updated properly. If a $digest is not in progress, it will cause a new $digest to run. – awe May 10 '16 at 08:50
  • Thanks for this answer! I've been searching since yesterday for a fix to this – CascadiaJS Nov 02 '16 at 17:54
  • Thanks me from the past for upvoting this answer. +1 for me. The $$phase stuff wasn't even working at all – Jelle den Burger Aug 01 '17 at 07:43
  • This should be the BEST answer for this question. It works better. However remember to clear the timeout like so : `var myTimer =$timeout(function(){/*code to execute here*/ $timeout.cancel(myTimer); })` – webicy Oct 30 '17 at 12:29
  • 2
    @webicy That's not a thing. When the body of the function passed to $timeout is run, the promise is already resolved! There's absolutely no reason to `cancel` it. From the [docs](https://docs.angularjs.org/api/ng/service/$timeout#cancel): "As a result of this, the promise will be resolved with a rejection." You can't resolve a resolved promise. Your cancellation won't cause any errors, but it won't do anything positive either. – daemone Feb 05 '18 at 10:30
673

Don't use this pattern - This will end up causing more errors than it solves. Even though you think it fixed something, it didn't.

You can check if a $digest is already in progress by checking $scope.$$phase.

if(!$scope.$$phase) {
  //$digest or $apply
}

$scope.$$phase will return "$digest" or "$apply" if a $digest or $apply is in progress. I believe the difference between these states is that $digest will process the watches of the current scope and its children, and $apply will process the watchers of all scopes.

To @dnc253's point, if you find yourself calling $digest or $apply frequently, you may be doing it wrong. I generally find I need to digest when I need to update the scope's state as a result of a DOM event firing outside the reach of Angular. For example, when a twitter bootstrap modal becomes hidden. Sometimes the DOM event fires when a $digest is in progress, sometimes not. That's why I use this check.

I would love to know a better way if anyone knows one.


From comments: by @anddoutoi

angular.js Anti Patterns

  1. Don't do if (!$scope.$$phase) $scope.$apply(), it means your $scope.$apply() isn't high enough in the call stack.
Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
Lee
  • 9,432
  • 3
  • 30
  • 34
  • 1
    Thanks! This saved my bacon. I was handling events from jQuery that were fired from elements in a list that had a filter on it. I got the error when I edited the contents of one of the elements in that list. Been scratching my a LOT over this issue and had been looking specifically for a way to detect if a $digest was in progress. The docs are useless for that sort of thing. – Joe Dyndale Oct 31 '12 at 13:14
  • 232
    Seems to me like $digest / $apply should do this by default – Roy Truelove Jan 11 '13 at 20:12
  • 21
    Note that in some cases I have to check but the current scope AND the root scope. I've been getting a value for $$phase on the root but not on my scope. Think it has something to do with a directive's isolated scope, but.. – Roy Truelove Jan 14 '13 at 15:33
  • 108
    "Stop doing `if (!$scope.$$phase) $scope.$apply()`", https://github.com/angular/angular.js/wiki/Anti-Patterns – anddoutoi Sep 05 '13 at 08:56
  • 1
    I'm getting this error without even calling $scope.$apply() or $scope.$digest() manually. It's just happening defaultly. – Jeremy Reed Oct 15 '13 at 15:53
  • 35
    @anddoutoi: Agreed; you're link makes it pretty clear this is not the solution; however, I'm uncertain what is meant by "you are not high enough in the call stack". Do you know what this means? – Trevor Nov 13 '13 at 19:19
  • 13
    @threed: see the answer by aaronfrost. The correct way is to use defer to trigger the digest in the next cycle. Otherwise the event will get lost and not update the scope at all. – Marek Dec 11 '13 at 09:45
  • also see http://stackoverflow.com/questions/22346990/why-is-using-ifscope-phase-scope-apply-an-anti-pattern to see why this is an anti-pattern – Dominik Goltermann Mar 12 '14 at 11:29
  • 7
    @anddoutoi Information value of your link is 0. Without further explanation why and how should programmer prevent the "anti-patterns" it's just worthless. – Petr Peller Apr 07 '14 at 13:02
  • 4
    -1 See [the answer by floribon](http://stackoverflow.com/a/23102223/3116322). Not only !$scope.$$phase isn't the right check - [$$ variables are private](http://stackoverflow.com/a/19338518/3116322), but ALSO there's actually no need for the check when a DOM event fires outside of angular. – Ande Jul 10 '14 at 15:50
  • 2
    @theed, about the "not high enough in the call stack": I think it means that if you cannot tell whether or not you need $apply, you can move the $apply code to a place where the function is called, and it'll be more obvious. I.e., you have two different paths to get to the same section of code, but only one of them needs the $apply call. If that happens, just call the function with $apply instead of trying to figure it out later! Hope that helps. – Jason Cox Jan 13 '15 at 17:59
  • 2
    @Bogdan yeah this is a common approach. Unfortunately it is a disgusting one, yet it seems like often the only valid solution. That, or it makes me feel like I really don't understand Angular. There should really be some better explanation/material around this issue, since is so fundamental to getting apps to function :( – dudewad Jun 29 '15 at 18:52
  • please edit your answer in order that the "updated" answer be in the first line, then the "wrong" answer, because people usually take the first solution they see – serge Nov 04 '15 at 16:04
  • Never use the private `$$` methods – gr3g Nov 18 '15 at 00:11
  • 2
    The advice saying "never do this" should be changed to "never do this, unless it's literally the only thing which works." I'm currently working with CKEditor, and I have to call $apply on the scope to apply the changes in the editor's handler, and I'm never certain if a digest is currently happening or not. So checking this is exactly what will fix the problem. – w0rp Dec 07 '15 at 14:55
  • @w0rp Sure, until it breaks on a future framework update. Does `evalAsync` not do what you want? – James Dec 07 '15 at 19:34
  • http://davidburgosonline.com/dev/2014/correctly-fix-angularjs-error-digest-already-in-progress/ has details on this error – Varshaan Dec 14 '15 at 09:47
  • 4
    Since this answer goes against best-practice as noted in @betaorbust's answer, and since the author of this answer begins by saying his proposal is not to be followed, this answer ought to be deleted. – Lee Goddard Jan 27 '16 at 10:23
  • 1
    When they say "isn't high enough in the call stack" they are suggesting that the current function or one of the functions calling it must be being called by at least 2 other functions, some of which are within the angular digest cycle and some of which are not. It is best practice to call $scope.$apply in the calling functions that are not in the digest cycle (without necessitating any sort of check). – csga5000 Feb 04 '17 at 16:56
  • 3
    I would say it's not always as simple as it "not being high enough in the call stack" if for example you're calling it in a callback from 3rd party code that *usually* returns after an event is fired but sometimes returns immediately because of issues (that can't be foreseen) then this issue is harder to address. – csga5000 Feb 04 '17 at 16:57
  • Here is my (human-readable) explanation, why to avoid checks on private $digest state: When you check and there're no running $digest cycle - eveyrthing is ok, you can call $apply(). When you check and there is already running $digest cycle - what will happen, if that running cycle ALREADY processed you scope (DOM node, associated with your scope)? Right, nothing will happen. So instead of this anti-pattern you should just delay your handler on next $digest cycle. This can be acheved multiple ways, but easiest is $timeout(() => { ...your code here...}); Note - without delay. – StNekroman Sama Apr 17 '20 at 07:05
333

The digest cycle is a synchronous call. It won't yield control to the browser's event loop until it is done. 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
});

or if you have lodash:

_.defer(function(){$scope.$apply();});

We tried several workarounds, and we hated injecting $rootScope into all of our controllers, directives, and even some factories. So, the $timeout and _.defer have been our favorite so far. These methods successfully tell angular to wait until the next animation loop, which will guarantee that the current scope.$apply is over.

Marwane Boudriga
  • 181
  • 1
  • 12
frosty
  • 21,036
  • 7
  • 52
  • 74
  • 1
    _.defer waits for the next event cycle. The current digest will be done by then, so the $scope.$apply will be free to run, since there will be no digest running. – frosty Sep 17 '13 at 06:06
  • 1
    @aaronfrost: Great! For debugging purposes: console.log('digest?: ', !!$scope.$$phase || !!$scope.$root.$$phase); _.defer(function() { console.log('digest?: ', !!$scope.$$phase || !!$scope.$root.$$phase); }); The first statement returns true if the $digest is processing; the second one will return false, as it is executed when the event loop has pop off all the tasks waiting. – roland Oct 13 '13 at 07:40
  • 2
    Is this comparable to using $timeout(...)? I've used $timeout in several cases to defer to the next event cycle and it seems to work fine--anyone know if there is a reason not to use $timeout? – Trevor Dec 11 '13 at 20:46
  • 9
    This should really only be used if you're already using `underscore.js`. This solution isn't worth importing the entire underscore library just to use its `defer` function. I much prefer the `$timeout` solution because everyone already has access to `$timeout` through angular, without any dependencies on other libraries. – tennisgent Jan 08 '14 at 19:22
  • 10
    True... but if you aren't using underscore or lodash... you need to reevaluate what you are doing. Those two libs have changed the way that code looks. – frosty Jan 08 '14 at 22:48
  • 1
    I tried to replace a `if (!$rootScope.$$phase)` with `_.defer()` in a directive, but this seems not to work in my situation. The directive makes a css transition, through `jQuery.animate()` and writes the width and height back into the scope during the animation steps. With `scope.$$phase` I have always a width and height value in the scope (a chart directive requires these values and throws an error, if they are not set), with `_.defer()` they seem to be set too late. I haven't analyzed it deeper, but in my situation `$rootScope.$$phase` works, while `_.defer()` doesn't. – stofl Mar 17 '14 at 11:46
  • 2
    We have lodash as a dependency for Restangular (we're going to eliminate Restangular in favor of ng-route soon). I think it's a good answer but it's not great to assume people want to use underscore/lodash. By all means those libs are fine... if you utilize them enough... these days I use ES5 methods which wipe out 98% of the reason I _used to_ include underscore. – BradGreens May 01 '14 at 19:30
  • 1
    The proper way to use angularjs is to use a dependency not built into nor mentioned in their docs? Something doesn't add up. – SgtPooki Jun 16 '14 at 21:24
  • 2
    You are right @SgtPooki. I modified the answer to include the option to use $timeout as well. $timeout and _.defer will both wait until the next animation loop, which will ensure that the current scope.$apply has ended. Thanks for keeping me honest, and getting me to update the answer here. – frosty Jun 17 '14 at 15:12
  • 1
    @aaronfrost thanks for improving the answer, but still, do the angularjs docs even suggest the $timeout method? I'm disappointed that such a prominent library has still failed to properly address this issue. They mention anti-patterns on their github page but have no suggestions for the pro-pattern that resolves this issue. Plus, it would be extremely trivial to implement their own defer/timeout method within $scope.$apply() that handles this scenario... bah, i'm rambling now. – SgtPooki Jun 17 '14 at 15:47
  • 2
    Call it old-hat, but using something called $(timeout) to essentially update the UI makes me absolutely *cringe*. Even if they just wrote a wrapper for it like "updateWhenPossible" or something stupid like that would calm me down. I agree with @SgtPooki - this should be addressed. – dudewad Jun 29 '15 at 18:56
  • 1
    "we hated injecting $rootScope into all of our controllers, directives, and even some factories" Why would you do that? Calling $scope.$apply() is something that should only be done RARELY in corner cases, not as a matter of course for regular functionality. – richard Oct 13 '15 at 10:04
  • 1
    @richard when you have a lot of integration with things outside of angular, like backbone, websockets, etc, you end up needing to call back into Angular more often that you want to. – frosty Oct 13 '15 at 14:48
  • @frosty Makes sense. I usually just try to keep to a stack that plays nice together. – richard Oct 13 '15 at 21:27
  • 1
    @richard as u should. I currently work on a project that is massive, started in backbone, and isn't fully coverted to angular yet. In these scenarios, you have to get back into angular often. – frosty Oct 14 '15 at 02:33
  • 1
    In case anyone was wondering (like I was), this does NOT work: `_.defer($scope.$apply);`. Frankly I'm still not sure why not... :-/ – Jeremy Moritz Jan 07 '16 at 17:39
  • It didn't work cause it was calling `$apply` in context of the window, not of the `$scope`. This would have worked: `_.defer($scope.$apply.bind($scope))`, cause you are binding the context of `$apply` to be `$scope`. – frosty Jan 07 '16 at 22:47
  • 1
    you need to inject `$timeout` otherwise it will give error.http://stackoverflow.com/a/19009389 – avck Jun 17 '16 at 18:38
  • 1
    This is the right answer. I was seeing this error when running "too close" .forEach sentences iterating business arrays; putting `_.defer(function(){$scope.$apply();});` between each other **fixed the error**. – julianm Jan 15 '20 at 15:04
273

Many of the answers here contain good advices but can also lead to confusion. Simply using $timeout is not the best nor the right solution. Also, be sure to read that if you are concerned by performances or scalability.

Things you should know

  • $$phase is private to the framework and there are good reasons for that.

  • $timeout(callback) will wait until the current digest cycle (if any) is done, then execute the callback, then run at the end a full $apply.

  • $timeout(callback, delay, false) will do the same (with an optional delay before executing the callback), but will not fire an $apply (third argument) which saves performances if you didn't modify your Angular model ($scope).

  • $scope.$apply(callback) invokes, among other things, $rootScope.$digest, which means it will redigest the root scope of the application and all of its children, even if you're within an isolated scope.

  • $scope.$digest() will simply sync its model to the view, but will not digest its parents scope, which can save a lot of performances when working on an isolated part of your HTML with an isolated scope (from a directive mostly). $digest does not take a callback: you execute the code, then digest.

  • $scope.$evalAsync(callback) has been introduced with angularjs 1.2, and will probably solve most of your troubles. Please refer to the last paragraph to learn more about it.

  • if you get the $digest already in progress error, then your architecture is wrong: either you don't need to redigest your scope, or you should not be in charge of that (see below).

How to structure your code

When you get that error, you're trying to digest your scope while it's already in progress: since you don't know the state of your scope at that point, you're not in charge of dealing with its digestion.

function editModel() {
  $scope.someVar = someVal;
  /* Do not apply your scope here since we don't know if that
     function is called synchronously from Angular or from an
     asynchronous code */
}

// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
  // No need to digest
  editModel();
}

// Any kind of asynchronous code, for instance a server request
callServer(function() {
  /* That code is not watched nor digested by Angular, thus we
     can safely $apply it */
  $scope.$apply(editModel);
});

And if you know what you're doing and working on an isolated small directive while part of a big Angular application, you could prefer $digest instead over $apply to save performances.

Update since Angularjs 1.2

A new, powerful method has been added to any $scope: $evalAsync. Basically, it will execute its callback within the current digest cycle if one is occurring, otherwise a new digest cycle will start executing the callback.

That is still not as good as a $scope.$digest if you really know that you only need to synchronize an isolated part of your HTML (since a new $apply will be triggered if none is in progress), but this is the best solution when you are executing a function which you cannot know it if will be executed synchronously or not, for instance after fetching a resource potentially cached: sometimes this will require an async call to a server, otherwise the resource will be locally fetched synchronously.

In these cases and all the others where you had a !$scope.$$phase, be sure to use $scope.$evalAsync( callback )

ojii
  • 4,729
  • 2
  • 23
  • 34
floribon
  • 19,175
  • 5
  • 54
  • 66
  • 4
    `$timeout` is critiqued in passing. Can you give more reasons to avoid `$timeout` ? – mlhDev May 03 '17 at 21:08
  • 1
    what about `$scope.applyAsync()` - no one has mentioned this one in any of the answers to this OP but I have seen it in other threads. When I changed all my `$scope.apply()` to `scope.applyAsync()` all my $digest cycle errors went away...don't know if I made more problems for myself, but so far no issues. – rolinger Jul 12 '22 at 12:30
89

Handy little helper method to keep this process DRY:

function safeApply(scope, fn) {
    (scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
lambinator
  • 10,616
  • 7
  • 55
  • 57
  • 6
    Your safeApply helped me understand what was going on a lot more than anything else. Thanks for posting that. – Jason More Jun 20 '13 at 21:55
  • 4
    I was about to do the same thing, but doesn't doing this mean there is a chance the changes we make in fn() won't be seen by $digest? Wouldn't it be better to delay the function, assuming scope.$$phase === '$digest' ? – Spencer Alger Sep 10 '13 at 00:16
  • I agree, sometimes $apply() is used to trigger the digest, just calling the fn by itself... won't that result in a problem? – CMCDragonkai Sep 17 '13 at 03:02
  • 1
    I feel like `scope.$apply(fn);` should be `scope.$apply(fn());` because fn() will execute the function and not fn. Please help me to where I am wrong – madhu131313 Jul 25 '15 at 15:00
  • @Alliswell The answer by Iambinator is actually working, but I haven't understood how it's working – madhu131313 Nov 14 '15 at 07:07
  • 1
    @ZenOut The call to $apply supports many different kinds of arguments, including functions. If passed a function, it evaluates the function. – boxmein Apr 07 '16 at 14:33
33

I had the same problem with third parties scripts like CodeMirror for example and Krpano, and even using safeApply methods mentioned here haven't solved the error for me.

But what do has solved it is using $timeout service (don't forget to inject it first).

Thus, something like:

$timeout(function() {
  // run my code safely here
})

and if inside your code you are using

this

perhaps because it's inside a factory directive's controller or just need some kind of binding, then you would do something like:

.factory('myClass', [
  '$timeout',
  function($timeout) {

    var myClass = function() {};

    myClass.prototype.surprise = function() {
      // Do something suprising! :D
    };

    myClass.prototype.beAmazing = function() {
      // Here 'this' referes to the current instance of myClass

      $timeout(angular.bind(this, function() {
          // Run my code safely here and this is not undefined but
          // the same as outside of this anonymous function
          this.surprise();
       }));
    }

    return new myClass();

  }]
)
Mistalis
  • 17,793
  • 13
  • 73
  • 97
Ciul
  • 633
  • 6
  • 7
32

See http://docs.angularjs.org/error/$rootScope:inprog

The problem arises when you have a call to $apply that is sometimes run asynchronously outside of Angular code (when $apply should be used) and sometimes synchronously inside Angular code (which causes the $digest already in progress error).

This may happen, for example, when you have a library that asynchronously fetches items from a server and caches them. The first time an item is requested, it will be retrieved asynchronously so as not to block code execution. The second time, however, the item is already in cache so it can be retrieved synchronously.

The way to prevent this error is to ensure that the code that calls $apply is run asynchronously. This can be done by running your code inside a call to $timeout with the delay set to 0 (which is the default). However, calling your code inside $timeout removes the necessity to call $apply, because $timeout will trigger another $digest cycle on its own, which will, in turn, do all the necessary updating, etc.

Solution

In short, instead of doing this:

... your controller code...

$http.get('some/url', function(data){
    $scope.$apply(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

do this:

... your controller code...

$http.get('some/url', function(data){
    $timeout(function(){
        $scope.mydate = data.mydata;
    });
});

... more of your controller code...

Only call $apply when you know the code running it will always be run outside of Angular code (e.g. your call to $apply will happen inside a callback that is called by code outside of your Angular code).

Unless someone is aware of some impactful disadvantage to using $timeout over $apply, I don't see why you couldn't always use $timeout (with zero delay) instead of $apply, as it will do approximately the same thing.

Trevor
  • 13,085
  • 13
  • 76
  • 99
  • Thanks, this worked for my case where I'm not calling `$apply` myself but still getting the error. – ariscris Mar 10 '14 at 14:51
  • 5
    The main difference is that `$apply` is synchronous (its callback is executed, then the code following $apply) while `$timeout`is not: the current code following timeout is executed, then a new stack begins with its callback, as if you were using `setTimeout`. That could lead to graphic glitches if you were updating twice the same model: `$timeout` will wait for the view to get refreshed before updating it again. – floribon Jun 01 '14 at 21:20
  • Thanks indeed, threed. I had a method called as a result of some $watch activity, and was trying to update the UI before my external filter had finished executing. Putting that inside a $timeout function worked for me. – djmarquette Jul 17 '14 at 14:56
29

When you get this error, it basically means that it's already in the process of updating your view. You really shouldn't need to call $apply() within your controller. If your view isn't updating as you would expect, and then you get this error after calling $apply(), it most likely means you're not updating the the model correctly. If you post some specifics, we could figure out the core problem.

dnc253
  • 39,967
  • 41
  • 141
  • 157
  • heh, I spent whole day to find out that AngularJS just can't watch bindings "magically" and I should push him sometimes with $apply(). – OZ_ May 20 '13 at 22:10
  • what at all means `you're not updating the the model correctly`? `$scope.err_message = 'err message';` is not correct update? – OZ_ May 20 '13 at 22:12
  • 2
    The only time you need to call `$apply()` is when you update the model "outside" of angular(e.g. from a jQuery plugin). It's easy to fall into the trap of the view not looking right, and so you throw a bunch of `$apply()`s everywhere, which then ends up with the error seen in the OP. When I say `you're not updating the the model correctly` I just mean all the business logic not correctly populating anything that might be in the scope, which leads to the view not looking as expected. – dnc253 May 21 '13 at 16:14
  • @dnc253 I agree, and I wrote the answer. Knowing what I know now, I would use $timeout(function(){...}); It does the same thing as _.defer does. They both defer to the next animation loop. – frosty Jun 17 '14 at 15:00
14

The shortest form of safe $apply is:

$timeout(angular.noop)
Anik Islam Abhi
  • 25,137
  • 8
  • 58
  • 80
Warlock
  • 7,321
  • 10
  • 55
  • 75
11

You can also use evalAsync. It will run sometime after digest has finished!

scope.evalAsync(function(scope){
    //use the scope...
});
CMCDragonkai
  • 6,222
  • 12
  • 56
  • 98
10

First of all, don’t fix it this way

if ( ! $scope.$$phase) { 
  $scope.$apply(); 
}

It does not make sense because $phase is just a boolean flag for the $digest cycle, so your $apply() sometimes won’t run. And remember it’s a bad practice.

Instead, use $timeout

    $timeout(function(){ 
  // Any code in here will automatically have an $scope.apply() run afterwards 
$scope.myvar = newValue; 
  // And it just works! 
});

If you are using underscore or lodash, you can use defer():

_.defer(function(){ 
  $scope.$apply(); 
});
Markus
  • 2,071
  • 4
  • 22
  • 44
Sagar M
  • 1,168
  • 13
  • 10
9

Sometimes you will still get errors if you use this way (https://stackoverflow.com/a/12859093/801426).

Try this:

if(! $rootScope.$root.$$phase) {
...
Community
  • 1
  • 1
bullgare
  • 1,643
  • 1
  • 21
  • 32
5

You should use $evalAsync or $timeout according to the context.

This is a link with a good explanation:

http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm

Anik Islam Abhi
  • 25,137
  • 8
  • 58
  • 80
Luc
  • 81
  • 1
  • 2
5

try using

$scope.applyAsync(function() {
    // your code
});

instead of

if(!$scope.$$phase) {
  //$digest or $apply
}

$applyAsync Schedule the invocation of $apply to occur at a later time. This can be used to queue up multiple expressions which need to be evaluated in the same digest.

NOTE: Within the $digest, $applyAsync() will only flush if the current scope is the $rootScope. This means that if you call $digest on a child scope, it will not implicitly flush the $applyAsync() queue.

Exmaple:

  $scope.$applyAsync(function () {
                if (!authService.authenticated) {
                    return;
                }

                if (vm.file !== null) {
                    loadService.setState(SignWizardStates.SIGN);
                } else {
                    loadService.setState(SignWizardStates.UPLOAD_FILE);
                }
            });

References:

1.Scope.$applyAsync() vs. Scope.$evalAsync() in AngularJS 1.3

  1. AngularJs Docs
eHayik
  • 2,981
  • 1
  • 21
  • 33
4

I would advise you to use a custom event rather than triggering a digest cycle.

I've come to find that broadcasting custom events and registering listeners for this events is a good solution for triggering an action you wish to occur whether or not you are in a digest cycle.

By creating a custom event you are also being more efficient with your code because you are only triggering listeners subscribed to said event and NOT triggering all watches bound to the scope as you would if you invoked scope.$apply.

$scope.$on('customEventName', function (optionalCustomEventArguments) {
   //TODO: Respond to event
});


$scope.$broadcast('customEventName', optionalCustomEventArguments);
3

yearofmoo did a great job at creating a reusable $safeApply function for us :

https://github.com/yearofmoo/AngularJS-Scope.SafeApply

Usage :

//use by itself
$scope.$safeApply();

//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);

//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {

});

//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {

});

//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);
Anik Islam Abhi
  • 25,137
  • 8
  • 58
  • 80
Jay Regal
  • 3,243
  • 1
  • 16
  • 18
2

I have been able to solve this problem by calling $eval instead of $apply in places where I know that the $digest function will be running.

According to the docs, $apply basically does this:

function $apply(expr) {
  try {
    return $eval(expr);
  } catch (e) {
    $exceptionHandler(e);
  } finally {
    $root.$digest();
  }
}

In my case, an ng-click changes a variable within a scope, and a $watch on that variable changes other variables which have to be $applied. This last step causes the error "digest already in progress".

By replacing $apply with $eval inside the watch expression the scope variables get updated as expected.

Therefore, it appears that if digest is going to be running anyways because of some other change within Angular, $eval'ing is all you need to do.

Charley B.
  • 74
  • 7
teleclimber
  • 318
  • 3
  • 7
2

use $scope.$$phase || $scope.$apply(); instead

Anik Islam Abhi
  • 25,137
  • 8
  • 58
  • 80
Visakh B Sujathan
  • 229
  • 1
  • 4
  • 25
1

Understanding that the Angular documents call checking the $$phase an anti-pattern, I tried to get $timeout and _.defer to work.

The timeout and deferred methods create a flash of unparsed {{myVar}} content in the dom like a FOUT. For me this was not acceptable. It leaves me without much to be told dogmatically that something is a hack, and not have a suitable alternative.

The only thing that works every time is:

if(scope.$$phase !== '$digest'){ scope.$digest() }.

I don't understand the danger of this method, or why it's described as a hack by people in the comments and the angular team. The command seems precise and easy to read:

"Do the digest unless one is already happening"

In CoffeeScript it's even prettier:

scope.$digest() unless scope.$$phase is '$digest'

What's the issue with this? Is there an alternative that won't create a FOUT? $safeApply looks fine but uses the $$phase inspection method, too.

paulmelnikow
  • 16,895
  • 8
  • 63
  • 114
SimplGy
  • 20,079
  • 15
  • 107
  • 144
  • 1
    I'd love to see an informed response to this question! – Ben Wheeler Sep 22 '14 at 15:41
  • It is a hack because it means you miss context or don't understand the code a this point: either you are within angular digest cycle and you don't need that, or you are asynchronously outside of that and then you need it. If you cannot know that in that point of the code, then you are not responsible to digest it – floribon Apr 30 '15 at 21:46
1

This is my utils service:

angular.module('myApp', []).service('Utils', function Utils($timeout) {
    var Super = this;

    this.doWhenReady = function(scope, callback, args) {
        if(!scope.$$phase) {
            if (args instanceof Array)
                callback.apply(scope, Array.prototype.slice.call(args))
            else
                callback();
        }
        else {
            $timeout(function() {
                Super.doWhenReady(scope, callback, args);
            }, 250);
        }
    };
});

and this is an example for it's usage:

angular.module('myApp').controller('MyCtrl', function ($scope, Utils) {
    $scope.foo = function() {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.foo);

    $scope.fooWithParams = function(p1, p2) {
        // some code here . . .
    };

    Utils.doWhenReady($scope, $scope.fooWithParams, ['value1', 'value2']);
};
ranbuch
  • 1,584
  • 16
  • 14
1

I have been using this method and it seems to work perfectly fine. This just waits for the time the cycle has finished and then triggers apply(). Simply call the function apply(<your scope>) from anywhere you want.

function apply(scope) {
  if (!scope.$$phase && !scope.$root.$$phase) {
    scope.$apply();
    console.log("Scope Apply Done !!");
  } 
  else {
    console.log("Scheduling Apply after 200ms digest cycle already in progress");
    setTimeout(function() {
        apply(scope)
    }, 200);
  }
}
Ashu
  • 639
  • 6
  • 11
1

When I disabled debugger , the error is not happening anymore. In my case, it was because of debugger stopping the code execution.

julianm
  • 2,393
  • 1
  • 23
  • 24
0

similar to answers above but this has worked faithfully for me... in a service add:

    //sometimes you need to refresh scope, use this to prevent conflict
    this.applyAsNeeded = function (scope) {
        if (!scope.$$phase) {
            scope.$apply();
        }
    };
Shawn Dotey
  • 616
  • 8
  • 11
0

You can use $timeout to prevent the error.

$timeout(function () {
    var scope = angular.element($("#myController")).scope();
    scope.myMethod(); 
    scope.$scope();
}, 1);
J.Hpour
  • 971
  • 11
  • 20
Satish Singh
  • 2,169
  • 3
  • 23
  • 32
0

The issue is basically coming when, we are requesting to angular to run the digest cycle even though its in process which is creating issue to angular to understanding. consequence exception in console.
1. It does not have any sense to call scope.$apply() inside the $timeout function because internally it does the same.
2. The code goes with vanilla JavaScript function because its native not angular angular defined i.e. setTimeout
3. To do that you can make use of

if(!scope.$$phase){
scope.$evalAsync(function(){

}); }

Sachin Mishra
  • 1,125
  • 1
  • 16
  • 17
0
        let $timeoutPromise = null;
        $timeout.cancel($timeoutPromise);
        $timeoutPromise = $timeout(() => {
            $scope.$digest();
        }, 0, false);

Here is good solution to avoid this error and avoid $apply

you can combine this with debounce(0) if calling based on external event. Above is the 'debounce' we are using, and full example of code

.factory('debounce', [
    '$timeout',
    function ($timeout) {

        return function (func, wait, apply) {
            // apply default is true for $timeout
            if (apply !== false) {
                apply = true;
            }

            var promise;
            return function () {
                var cntx = this,
                    args = arguments;
                $timeout.cancel(promise);
                promise = $timeout(function () {
                    return func.apply(cntx, args);
                }, wait, apply);
                return promise;
            };
        };
    }
])

and the code itself to listen some event and call $digest only on $scope you need

        let $timeoutPromise = null;
        let $update = debounce(function () {
            $timeout.cancel($timeoutPromise);
            $timeoutPromise = $timeout(() => {
                $scope.$digest();
            }, 0, false);
        }, 0, false);

        let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
            $update();
        });


        $scope.$on('$destroy', () => {
            $timeout.cancel($update);
            $timeout.cancel($timeoutPromise);
            $unwatchModelChanges();
        });
Sergey Sahakyan
  • 723
  • 8
  • 15
-3

Found this: https://coderwall.com/p/ngisma where Nathan Walker (near bottom of page) suggests a decorator in $rootScope to create func 'safeApply', code:

yourAwesomeModule.config([
  '$provide', function($provide) {
    return $provide.decorator('$rootScope', [
      '$delegate', function($delegate) {
        $delegate.safeApply = function(fn) {
          var phase = $delegate.$$phase;
          if (phase === "$apply" || phase === "$digest") {
            if (fn && typeof fn === 'function') {
              fn();
            }
          } else {
            $delegate.$apply(fn);
          }
        };
        return $delegate;
      }
    ]);
  }
]);
Warren Davis
  • 333
  • 3
  • 4
-7

This will be solve your problem:

if(!$scope.$$phase) {
  //TODO
}
eebbesen
  • 5,070
  • 8
  • 48
  • 70
  • Don't do if (!$scope.$$phase) $scope.$apply(), it means your $scope.$apply() isn't high enough in the call stack. – MGot90 May 13 '15 at 15:28