As far as i know, $timeout is a promise object in angular, which means the code will go on running without waiting for the timeout to end.
However, when i used it in my ionic code, for some reason it did and the whole loading of the page froze for 6 seconds. Can you please explain why?
$scope.$on("$ionicView.Enter", function( scopes, states ) {
$timeout(function(){
// some function i wrote
}, 6000);
});
Your assumption around the code continuing to run is wrong - otherwise what would the point of calling $timeout be? It's an Angular wrapper and recommended to use in place of window.setTimeout() but works exactly the same. The code above will execute after a 6000ms delay.
[ADDED] From the Angular API docs: "The return value of calling $timeout is a promise, which will be resolved when the delay has passed and the timeout function, if provided, is executed."
Related
while calling function getting this error
Error: "[$rootScope:inprog] http://errors.angularjs.org/1.6.9/$rootScope/inprog?p0=%24digest"
But if calling it from console it is working fine.
angular.element('body').scope().set_active_counter(1);
And function looks like this
$scope.set_active_counter = function (i) {
$scope.$apply(function () {
$scope.active_question_counter = i;
});
active_question_counter = i;
}
"inprog" typically means there's a digest in progress. Calling $scope.$apply() manually is the cause. The only time you should ever need to manually call $scope.$apply() is if it's inside some kind of async-ish call like the callback from $http or a promise.
Assigning a value to the $scope variable "active_question_counter" should automatically trigger a digest. But because you have manually triggered the digest, and within the scope of that digest you are making the change, the result is a "digest already in progress" error.
You shouldn't need to explicitly call $apply() here unless there's some weird behind-the-scenes linking going on that doesn't actively detect that something has changed. If that is the case, a hacky workaround is to wrap the $scope.$apply(...) inside a setTimeout().
I'm not really sure why executing this through angular.elment().scope() in the console works. Maybe the console is running in a separate context?
I am having an issue where I have a function like this...
$rootScope.canNavigate = function(stateName) {
return !stateName || Authentication.canNavigate.call(Authentication, $state.get(stateName));
};
The problem is this function gets called continuously. The stack trace is different every time but has one thing in common, it is always coming from $apply. I have commented out all of the watches that are using this function and it is still happening. Does anyone know why this is happening? I cannot seem to reproduce in a plunker.
Asiel probably has the right answer depending on your situation, but as to how to prevent it, you can either use the bind once expression as Asiel suggests or, if that's not possible for some reason, you could also save the results so you're not calling that service every time.
var canNavigateCache = {};
$rootScope.canNavigate = function(stateName) {
return !stateName
|| canNavigateCache[stateName]
|| canNavigateCache[stateName] =
Authentication.canNavigate.call(Authentication, $state.get(stateName));
};
This also gives you the ability to clear the cache if you need to by just resetting the cache object (canNavigateCache = {})
If there is a call of this function on any of your html, this function will be called every time angular executes a digest cycle in order to do dirty checking (See this question on SO in order to get some kind of reference about this)... except you use the bind once expression (See syntax on this article and this on SO)
It is called from $apply because
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life cycle of
exception handling, executing watches.
but $apply is (also) used by the own framework for making a digest cycle.
I'm trying to better understand the nuances of using the $timeout service in Angular as a sort of "safe $apply" method. Basically in scenarios where a piece of code could run in response to either an Angular event or a non-angular event such as jQuery or some standard DOM event.
As I understand things:
Wrapping code in $scope.$apply works fine for scenarios where you
aren't already in a digest loop (aka. jQuery event) but will raise an error if a digest is in progress
Wrapping code in a $timeout() call with no delay parameter works whether already in a digest cycle or not
Looking at Angular source code, it looks like $timeout makes a call to $rootScope.$apply().
Why doesn't $timeout() also raise an error if a digest cycle is already in progress?
Is the best practice to use $scope.$apply() when you know for sure that a digest won't already be in progress and $timeout() when needing it to be safe either way?
Is $timeout() really an acceptable "safe apply", or are there gotchas?
Thanks for any insight.
Looking at Angular source code, it looks like $timeout makes a call to
$rootScope.$apply().
Why doesn't $timeout() also raise an error if a digest cycle is already in progress?
$timeout makes use of an undocumented Angular service $browser. Specifically it uses $browser.defer() that defers execution of your function asynchronously via window.setTimeout(fn, delay), which will always run outside of Angular life-cycle. Only once window.setTimeout has fired your function will $timeout call $rootScope.$apply().
Is the best practice to use $scope.$apply() when you know for sure that a digest won't already be in progress and $timeout() when needing it to be safe either way?
I would say so. Another use case is that sometimes you need to access a $scope variable that you know will only be initialized after digest. Simple example would be if you want to set a form's state to dirty inside your controller constructor (for whatever reason). Without $timeout the FormController has not been initialized and published onto $scope, so wrapping $scope.yourform.setDirty() inside $timeout ensures that FormController has been initialized. Sure you can do all this with a directive without $timeout, just giving another use case example.
Is $timeout() really an acceptable "safe apply", or are there gotchas?
It should always be safe, but your go to method should always aim for $apply() in my opinion. The current Angular app I'm working on is fairly large and we've only had to rely on $timeout once instead of $apply().
If we use $apply heavily in the application, we might get the Error: $digest already in progress. It happens because one $digest cycle can be run at a time. We can resolve it by $timeout or by $evalAsync.
The $timeout does not generate error like "$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
I tried to explain them at : Comparison of apply, timeout,digest and evalAsync.
May be it will help you.
As far as I understand it, $timeout is a wrapper around setTimeout which implicitly calls $scope.$apply, meaning it runs outside of the angular lifecycle, but kickstarts the angular lifecycle itself. The only "gotcha" I can think of is that if you're expecting your result to be available this $digest, you need to find another way to "safe apply" (which, AFAIK, is only available via $scope.$$phase).
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.
You can see the problem in this plunk with Chrome or FireFox's console open:
http://plnkr.co/edit/jmDcxqQ48PmeHt3EPVMU
When you click "Push!" for the first time, the watch function doesn't execute. But if you push it again, it does execute. It's like it ignores the first push. However, you can clearly see the dataPoints array changes after the first push due to the timeout function that is executing in the background. What's weirder, is if you comment out the jQuery fadeIn wrapper, the code works as expected (the watch function is executed on the first button push).
What's going on here? Why is the $watch function not executing when jQuery().fadeIn is used?
jQuery('#mytest').fadeIn(200, function() {
$scope.mydata.dataPoints.push(1);
$scope.$$phase || $scope.$apply();
})
When the fadeIn callback runs, it runs "outside" Angular, so we need to call $scope.$apply() to run a digest cycle. However, if the object is already visible, it seems that jQuery runs the callback immediately (so we're still "inside" Angular), so we don't need to (and shouldn't) call $scope.$apply().
The easy way to handle both cases is to check to see if we are already in a digest cycle using private property $$phase. A cleaner implementation would be to not call fadeIn() if the div is already visible.