Debugging $digest already in progress error
Asked Answered
O

1

13

I'm building a complex hybrid app and have been testing on a real device. Occasionally, I'm getting the dreaded $digest already in progress error from Angular - especially, it appears to be after a somewhat long digest cycle. From the stack trace it appears to be initiated from an Angular defer function that updates the location.href, which then triggers fastclick to send a touchend that in turn triggers a second digest leading to the error. Has anyone experienced this same error - and if so, how did you go about resolving it?

For those interested, here is what I am seeing in the stacktrace:

Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.3.0/$rootScope/inprog?p0=%24digest:
file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:80:32
beginPhase@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:14473:31
$apply@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:14220:21
file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:22523:29
eventHandler@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:3013:25
dispatchEvent@sendClick@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/fastclick/fastclick.js:295:30
onTouchEnd@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/fastclick/fastclick.js:589:18
file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/fastclick/fastclick.js:105:43
url@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:5022:19
setBrowserUrlWithFallback@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:11080:21
file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:11186:40
$eval@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:14123:28
$digest@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:13939:36
file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:14161:33
completeOutstandingRequest@file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:4877:15
file:///private/var/mobile/Containers/Bundle/Application/4040564A-5631-4A1A-B2FD-6E53F9A574F2/test.app/www/js/3rdparty/angular/angular.js:5250:33
Orcein answered 1/5, 2015 at 18:40 Comment(6)
somewhere in application you must be doing $scope.$apply()Forestation
I tend not to use $apply and favor $timeout to make sure I avoid the $digest in progress error. However, I will take a second look if there is any rogue $apply call. Thanks.Orcein
It's not about $apply in the application, and it's clearly visible here. The only criminals are FastClick and ng-click directive of Angular.Scholl
What about delaying the event dispatching inside fastClick? Like, replacing targetElement.dispatchEvent(clickEvent); with setTimeout(function() { targetElement.dispatchEvent(clickEvent); }, 0) or something similar.Scholl
@Scholl are you talking about to change code in fastClick.js plugin?Forestation
Yes, right there. Technically, it seems to be correct; there's something wrong when one event is fired 'within' another. With setTimeout, we clearly separate those.Scholl
S
4

Here's the havoc wreaker (source link):

FastClick.prototype.sendClick = function(targetElement, event) {
    // ... some lines skipped

    clickEvent = document.createEvent('MouseEvents');
    clickEvent.forwardedTouchEvent = true;
    targetElement.dispatchEvent(clickEvent); // got you!
};

The problem is that the handler for the artificial click will be fired immediately (demo). That's usually fine - but not with Angular, because touchEnd event has been generated within $digest phase, and ng-click seems to be somewhat optimistic about events it has to process with its $apply code. Hence the $digest ka-boom.

Anyway, this thing seems to be pretty fixable: just async the event dispatch! One, rather straightforward way is replacing the got you! line with something like...

setTimeout(function() {
  targetElement.dispatchEvent(clickEvent);
}, 0);

Seems to work in our case, at least. )

Scholl answered 4/6, 2015 at 19:16 Comment(6)
Can you explain what you mean by "fired immediately"? I can't understand how onTouchEnd gets triggered in the call stack of setBrowserUrlWithFallback and url, which are part of a url redirection.Directly
I reported a similar problem hereDirectly
Technically, here's what happens: 1) AJAX request is finished, so $digest is fired [Angular]; 2) url is changed by the routing component [Angular], and that somehow triggers touchEnd event; 3) that event is handled by fastClick plugin that whole purpose is to 'translate' it into click event; 4) click event is fired and processed by ng-click while the $digest still in place; 5) boom!Scholl
The part I don't understand is step 2, why would the routing component trigger touchEnd? That seems wrong to me. I get the feeling that fastclick is binding into some event that it shouldn't and may be delaying the app overall because of this. This only happens with iOS inside the Cordova app, for us.Directly
@Directly "why would the routing component trigger touchEnd" - well, that's definitely the question to be solved. But that's exactly what we see in the error stack. And yes, it only happens on Cordova-build apps inside iOS.Scholl
This seems right. Same proposed solution here: github.com/ftlabs/fastclick/issues/466Mcglynn

© 2022 - 2024 — McMap. All rights reserved.