ChatGPT解决这个技术问题 Extra ChatGPT

Angular $scope.$apply vs $timeout as a safe $apply

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.


C
Community

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().


In my project, I have a scenario where i need to raise the $scope.$digest() to capture the events happened outside Angular's view. Is Timeout still the norm for safeApply or safeDigest or this has been smartly fixed inside the framework?
Why did you need to rely on $timeout instead of $apply? If you can't share code, could you at least discuss the basic reason?
R
Rahul Garg

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.


Thanks Rahul, article is interesting. What I felt it was missing though was a recommendation of which to use, or which to use when. Thanks again.
Thanks Matty for the input. In my opinion, $evalAsync is better option as compare to available one.
J
Jeff Hubbard

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).