The $q service is very powerful in angularjs and make our life easier with asynchronous code.
I am new to angular but using deferred API is not very new to me. I must say that I completely ok with the How to use part of documentation + there are very useful links for that within the docs + I checked out the source either.
My question is more about the under the hood parts of deferred and promise API objects in angular. What are the exact phases in their life cycles and how are they interacts with rootScope.Scope(s). My assumptions are that when the promise resolves - it invokes the digest loop ??? yes / no ?
Can one provide a detailed answer with specific respect to the following list of aspects:
What is the order of things that happen in each of your described
steps / phases
When new deferred object with a new promise instance created - who aware of it / is it
important ?
How exactly the scope updated when promise object being resolved? Do i have to update it manually inside the callback or the digest will be automatically invoked and update the rootScope like declared here
mention at least one approach of updating the scope from within the promise callback
I assume there a lot of other useful aspects, feel free to provide them all.
I will appreciate and accept the most detailed answer, with as much as possible references to docs or source (that i couldn't find by myself). I can't find any previously discussion with this topic, if there already was - please post links.
ps: +1 for any one that will help by suggesting a better title for this question, please add your suggestions in a comment.
Cheers!
Promises have three states
Pending - this is how promises start.
Fulfilled - this is what happens when you resolve a deferred, or when the return value from .then fulfills, and it generally analogous to a standard return value.
Rejected - This is what happens when you reject a deferred, when you throw from a .then handler or when you return a promise that unwraps to a rejection*, it is generally analogous to a standard exception thrown.
In Angular, promises resolve asynchronously and provide their guarantees by resolving via $rootScope.$evalAsync(callback); (taken from here).
Since it is run via $evalAsync we know that at least one digest cycle will happen after the promise resolves (normally), since it will schedule a new digest if one is not in progress.
This is also why for example when you want to unit test promise code in Angular, you need to run a digest loop (generally, on rootScope via $rootScope.digest()) since $evalAsync execution is part of the digest loop.
Ok, enough talk, show me the code:
Note: This shows the code paths from Angular 1.2, the code paths in Angular 1.x are all similar but in 1.3+ $q has been refactored to use prototypical inheritance so this answer is not accurate in code (but is in spirit) for those versions.
1) When $q is created it does this:
this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
return qFactory(function(callback) {
$rootScope.$evalAsync(callback);
}, $exceptionHandler);
}];
Which in turn, does:
function qFactory(nextTick, exceptionHandler) {
And only resolves on nextTick passed as $evalAsync inside resolve and notify:
resolve: function(val) {
if (pending) {
var callbacks = pending;
pending = undefined;
value = ref(val);
if (callbacks.length) {
nextTick(function() {
var callback;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
callback = callbacks[i];
value.then(callback[0], callback[1], callback[2]);
}
});
}
}
},
On the root scope, $evalAsync is defined as:
$evalAsync: function(expr) {
// if we are outside of an $digest loop and this is the first time we are scheduling async
// task also schedule async auto-flush
if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
$browser.defer(function() {
if ($rootScope.$$asyncQueue.length) {
$rootScope.$digest();
}
});
}
this.$$asyncQueue.push({scope: this, expression: expr});
},
$$postDigest : function(fn) {
this.$$postDigestQueue.push(fn);
},
Which, as you can see indeed schedules a digest if we are not in one and no digest has previously been scheduled. Then it pushes the function to the $$asyncQueue.
In turn inside $digest (during a cycle, and before testing the watchers):
asyncQueue = this.$$asyncQueue,
...
while(asyncQueue.length) {
try {
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression);
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
lastDirtyWatch = null;
}
So, as we can see, it runs on the $$asyncQueue until it's empty, executing the code in your promise.
So, as we can see, updating the scope is simply assigning to it, a digest will run if it's not already running, and if it is, the code inside the promise, run on $evalAsync is called before the watchers are run. So a simple:
myPromise().then(function(result){
$scope.someName = result;
});
Suffices, keeping it simple.
* note angular distinguishes throws from rejections - throws are logged by default and rejections have to be logged explicitly
when the promise resolves - it invokes the digest loop ?
Yes. You can test this by a simple template:
{{state}}
and controller code that changes the $scope.state variable after a delay:
$scope.state = 'Pending';
var d = $q.defer();
d.promise.then(function() {
$scope.state = 'Resolved, and digest cycle must have run';
});
$window.setTimeout(function() {
d.resolve();
}, 1000);
You can see this at http://plnkr.co/edit/fIfHYz9EYK14A5OS6NLd?p=preview. After a second, the text in the HTML shows Resolved, and digest cycle must have run. The call to setTimeout rather than $timeout is deliberate, to ensure that it must be resolve which ends up starting the digest loop.
This is confirmed by looking in the source: resolve calls its callbacks via nextTick, which is a function that passed the callbacks to $rootScope.$evalAsync, which according to the $evalAsync docs:
at least one $digest cycle will be performed after expression execution
Those same docs also say:
Note: if this function is called outside of a $digest cycle, a new $digest cycle will be scheduled
so whether the stack is already in the $digest loop can change the exact order of events.
'1. What is the order of things that happen in each of your described steps / phases
Going into the previous example in detail:
var d = $q.defer();
The deferred object's promise is in a pending state. At this point, virtually nothing has happened, you just have a deferred object with resolve, reject, notifiy and promise properties. Nothing has used or affected the $digest loop
d.promise.then(function() {
$scope.state = 'Resolved, and digest cycle must have run';
});
The promise is still in a pending state, but with a then success callback registered. Again, nothing has used or affected the $digest loop or any scope.
$window.setTimeout(function() {
d.resolve();
}, 1000);
After 1 second, d.resolve will be called. This passes the callback defined in Step 2 above to $evalAsync (via nextTick).
$evalAsync will call the callback
$evalAsync will then ensure one $digest cycle will be called.
'2. When new deferred object with a new promise instance created - who aware of it / is it important ?
Only the caller of $q.defer(). Nothing happens with respect to any scope until resolved is called (or indeed, reject or notify).
'3. How exactly the scope updated promise object being resolved? Do i have to update it manually inside the callback or the digest will be automatically invoked and update the rootScope like declared here
As mentioned, the $digest loop will be automatically started by calling resolve (if it's not in it already).
'4. mention at least one approach of updating the scope from within the promise callback
The example above gives this.
'5. I assume there a lot of other useful aspects, feel free to provide them all.
Not that I can think of!
Sometimes I need to use $scope.$apply in my code and sometimes it throws a "digest already in progress" error. So I started to find a way around this and found this question: AngularJS : Prevent error $digest already in progress when calling $scope.$apply(). However in the comments (and on the angular wiki) you can read:
Don't do if (!$scope.$$phase) $scope.$apply(), it means your $scope.$apply() isn't high enough in the call stack.
So now i have two questions:
Why exactly is this an anti-pattern?
How can i safely use $scope.$apply?
Another "solution" to prevent "digest already in progress" error seems to be using $timeout:
$timeout(function() {
//...
});
Is that the way to go? Is it safer? So here is the real question: How I can entirely eliminate the possibility of a "digest already in progress" error?
PS: I am only using $scope.$apply in non-angularjs callbacks that are not synchronous. (as far as I know those are situations where you must use $scope.$apply if you want your changes to be applied)
After some more digging i was able to solve the question whether it's always safe to use $scope.$apply. The short answer is yes.
Long answer:
Due to how your browser executes Javascript, it is not possible that two digest calls collide by chance.
The JavaScript code we write doesn’t all run in one go, instead it executes in turns. Each of these turns runs uninterupted from start to finish, and when a turn is running, nothing else happens in our browser. (from http://jimhoskins.com/2012/12/17/angularjs-and-apply.html)
Hence the error "digest already in progress" can only occur in one situation: When an $apply is issued inside another $apply, e.g.:
$scope.apply(function() {
// some code...
$scope.apply(function() { ... });
});
This situation can not arise if we use $scope.apply in a pure non-angularjs callback, like for example the callback of setTimeout. So the following code is 100% bulletproof and there is no need to do a if (!$scope.$$phase) $scope.$apply()
setTimeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
even this one is safe:
$scope.$apply(function () {
setTimeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
});
What is NOT safe (because $timeout - like all angularjs helpers - already calls $scope.$apply for you):
$timeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
This also explains why the usage of if (!$scope.$$phase) $scope.$apply() is an anti-pattern. You simply don't need it if you use $scope.$apply in the correct way: In a pure js callback like setTimeout for example.
Read http://jimhoskins.com/2012/12/17/angularjs-and-apply.html for the more detailed explanation.
It is most definitely an anti-pattern now. I've seen a digest blow up even if you check for the $$phase. You're just not supposed to access the internal API denoted by $$ prefixes.
You should use
$scope.$evalAsync();
as this is the preferred method in Angular ^1.4 and is specifically exposed as an API for the application layer.
In any case when your digest in progress and you push another service to digest, it simply gives an error i.e. digest already in progress.
so to cure this you have two option.
you can check for anyother digest in progress like polling.
First one
if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
$scope.$apply();
}
if the above condition is true, then you can apply your $scope.$apply otherwies not and
second solution is use $timeout
$timeout(function() {
//...
})
it will not let the other digest to start untill $timeout complete it's execution.
scope.$apply triggers a $digest cycle which is fundamental to 2-way data binding
A $digest cycle checks for objects i.e. models(to be precise $watch) attached to $scope to assess if their values have changed and if it detects a change then it takes necessary steps to update the view.
Now when you use $scope.$apply you face an error "Already in progress" so it is quite obvious that a $digest is running but what triggered it?
ans--> every $http calls, all ng-click, repeat, show, hide etc trigger a $digest cycle AND THE WORST PART IT RUNS OF EVERY $SCOPE.
ie say your page has 4 controllers or directives A,B,C,D
If you have 4 $scope properties in each of them then you have a total of 16 $scope properties on your page.
If you trigger $scope.$apply in controller D then a $digest cycle will check for all 16 values!!! plus all the $rootScope properties.
Answer-->but $scope.$digest triggers a $digest on child and same scope so it will check only 4 properties. So if you are sure that changes in D will not affect A, B, C then use $scope.$digest not $scope.$apply.
So a mere ng-click or ng-show/hide might be triggering a $digest cycle on over 100+ properties even when the user has not fired any event!
Use $timeout, it is the way recommended.
My scenario is that I need to change items on the page based on the data I received from a WebSocket. And since it is outside of Angular, without the $timeout, the only model will be changed but not the view. Because Angular doesn't know that piece of data has been changed. $timeout is basically telling Angular to make the change in the next round of $digest.
I tried the following as well and it works. The difference to me is that $timeout is clearer.
setTimeout(function(){
$scope.$apply(function(){
// changes
});
},0)
I found very cool solution:
.factory('safeApply', [function($rootScope) {
return function($scope, fn) {
var phase = $scope.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (fn) {
$scope.$eval(fn);
}
} else {
if (fn) {
$scope.$apply(fn);
} else {
$scope.$apply();
}
}
}
}])
inject that where you need:
.controller('MyCtrl', ['$scope', 'safeApply',
function($scope, safeApply) {
safeApply($scope); // no function passed in
safeApply($scope, function() { // passing a function in
});
}
])
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
$$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 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!
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".
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.
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.
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?
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!
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
Don't do if (!$scope.$$phase) $scope.$apply(), it means your $scope.$apply() isn't high enough in the call stack.
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.
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 )
Handy little helper method to keep this process DRY:
function safeApply(scope, fn) {
(scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
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();
}]
)
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.
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.
The shortest form of safe $apply is:
$timeout(angular.noop)
You can also use evalAsync. It will run sometime after digest has finished!
scope.evalAsync(function(scope){
//use the scope...
});
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();
});
Sometimes you will still get errors if you use this way (https://stackoverflow.com/a/12859093/801426).
Try this:
if(! $rootScope.$root.$$phase) {
...
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
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
AngularJs Docs
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);
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);
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.
use $scope.$$phase || $scope.$apply(); instead
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.
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']);
};
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);
}
}
When I disabled debugger , the error is not happening anymore. In my case, it was because of debugger stopping the code execution.
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();
}
};
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(){
});
}
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();
});
You can use $timeout to prevent the error.
$timeout(function () {
var scope = angular.element($("#myController")).scope();
scope.myMethod();
scope.$scope();
}, 1);
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;
}
]);
}
]);
This will be solve your problem:
if(!$scope.$$phase) {
//TODO
}