4

Sometimes i need to use $scope.$apply, for example when i use jQuery ajax or some non-angular-js eventlisteners. In those cases i have an asynchronous callback and there i use $scope.$apply so that changes to the scope are picked up by angular and watches are triggered. Please correct me if that is the wrong use of $scope.$apply.

This seems to work and the view is updated. However in some rather rare cases I get a "digest already in progress" error. I don't see how this can be possible, since the callback is not synchronous. So I wonder if it is possible that my asynchronous callback with the $scope.$apply in it can by chance collide with an ongoing digest? If so, how can I prevent this from happening?

edit:

One possibility to check for a digest is checking $$phase: if (!$scope.$$phase) $scope.$apply() but this is an anti-pattern as the angularjs wiki says: https://github.com/angular/angular.js/wiki/Anti-Patterns

I want to fully understand why it is possible that I encounter a digest in an asynchronous callback. And why this is an antipattern.

Dominik Goltermann
  • 4,276
  • 2
  • 26
  • 32
  • Mind if I ask why you have jQuery AJAX and non-angular listeners floating around an Angular app? If it's old legacy code waiting to be updated, `$apply` is probably your best bet, but as you can see you run the risk of a digest already happening. – tymeJV Mar 11 '14 at 16:17
  • most of the time it's inside libraries I am using :/ another example is in phonegap i'm using https://github.com/apache/cordova-plugin-inappbrowser where i want to listen to the load event of a new browser window – Dominik Goltermann Mar 11 '14 at 16:42

4 Answers4

1

You are calling $apply on an existing scope, so it's definitely possible that you are calling apply while it is currently digesting. Some people might suggest checking $$phase but that has been discouraged as an anti-pattern

You have two options if you are running into this problem, even occasionally. One is to do as the anti-pattern implies and make sure your $scope.$apply is as high as possible up the chain.

However, this won't help if you are asynchronously calling $apply on the same $scope in rapid succession. The answer then is often to throttle the $apply so that it only happens every few hundred milliseconds. This is a common practice when using something like socket.io that can fire events which could cause you to re-apply many times.

Jordan.J.D
  • 7,999
  • 11
  • 48
  • 78
Matt Pileggi
  • 7,126
  • 4
  • 16
  • 18
  • i already read that checking $$phase is an anti-pattern. But can you explain why and what do you mean by as high as possible up the chain? How exactly is it related to the "digest already in progress" error? – Dominik Goltermann Mar 11 '14 at 16:40
  • Since scopes are created in a hierarchy - eg: a controller in a controller, a directive not isolate scope - you could be calling $apply on a child scope while the parent scope is already digesting with that scope from some other change. So your $apply should occur on the parent-most scope in those cases. It still wouldn't fix it if you are directly calling $apply on that same scope before it has completed digesting. Does your $apply take a long time? You could debug with $$phase to see when it happens. I suspect your async $apply calls are at times lagging very closely to each other. – Matt Pileggi Mar 11 '14 at 16:47
  • thaks for your reply. I don't think those async calls are not close to each other but I cant't really say. One example is the geolocation plugin https://github.com/arunisrael/angularjs-geolocation (i haven't had a close look at how it uses apply yet) which threw me a digest error yesterday. As i understand i should use the rootScope to apply when possible to minimize potential digest errors. but often I cant't do this and there is always the possibility that a digest error occurs. right? so how do I completely eliminate that possibility? Using the anti-pattern? – Dominik Goltermann Mar 12 '14 at 09:12
  • 1
    You can certainly prevent it using a check for $$phase. It shouldn't cause any issues but it's just masking a symptom. However, if you've tried other fixes and it still happens rules were meant to be broken! – Matt Pileggi Mar 12 '14 at 13:03
  • @matt-pileggi can you give an example of throttling the $apply as well as some citation of $$phase being an anti patern, when using event streaming it's something that people frequently run into and $$phase is almost always said to be the solution. – Gent Mar 13 '14 at 15:59
  • Sure, check out this article http://briantford.com/blog/huuuuuge-angular-apps.html and scroll down to "Optimizing the Digest Cycle" which deals with sockets in particular. And the link in my answer has a short list of anti-patterns https://github.com/angular/angular.js/wiki/Anti-Patterns – Matt Pileggi Mar 14 '14 at 13:31
1

I recently asked a similiar question on why if (!$scope.$$phase) $scope.$apply() is an anti-pattern here: Why is using if(!$scope.$$phase) $scope.$apply() an anti-pattern?

The answer there also contains the answer to this question.

Community
  • 1
  • 1
Dominik Goltermann
  • 4,276
  • 2
  • 26
  • 32
0

I think, that its normal, but it will be better to move code to services and resources with angular style. To prevent error you can check current state

scope.$$phase

To prevent calling $apply

Vasiliy vvscode Vanchuk
  • 7,007
  • 2
  • 21
  • 44
0

Async Callbacks are an appropriate time to use the $apply, there are edgecases I have run into as well where i get that error. They are usually because I am doing something wonky, I have found that a "Conditional Apply" works well for these.

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

AngularJS : Prevent error $digest already in progress when calling $scope.$apply()

Community
  • 1
  • 1
Gent
  • 2,675
  • 1
  • 24
  • 34
  • what do you mean by wonky and how does it cause a digest error? – Dominik Goltermann Mar 12 '14 at 09:15
  • When I have run into this in the past is was because I was using an eventing framework and multiple events where coming across the wire at near about the same time, so I would have code that handled an event and update the page then have another event come in and modify the data inside of an $apply and get the error. Seemingly because angular was still in the process of digesting the last changes. – Gent Mar 12 '14 at 13:17