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
raina77ow
  • 103,633
  • 15
  • 192
  • 229
kamp121
  • 133
  • 4
  • 3
    somewhere in application you must be doing `$scope.$apply()` – Pankaj Parkar May 01 '15 at 18:51
  • 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. – kamp121 May 01 '15 at 19:06
  • 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. – raina77ow Jun 03 '15 at 15:11
  • What about delaying the event dispatching inside fastClick? Like, replacing `targetElement.dispatchEvent(clickEvent);` with `setTimeout(function() { targetElement.dispatchEvent(clickEvent); }, 0)` or something similar. – raina77ow Jun 03 '15 at 18:11
  • @raina77ow are you talking about to change code in fastClick.js plugin? – Pankaj Parkar Jun 03 '15 at 18:40
  • 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. – raina77ow Jun 03 '15 at 18:45

1 Answers1

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

raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • 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. – pauloya Apr 25 '16 at 11:49
  • I reported a similar problem [here](https://github.com/ftlabs/fastclick/issues/466) – pauloya Apr 25 '16 at 11:51
  • 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! – raina77ow Apr 25 '16 at 13:05
  • 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. – pauloya Apr 25 '16 at 13:09
  • @pauloya "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. – raina77ow Apr 25 '16 at 18:07
  • This seems right. Same proposed solution here: https://github.com/ftlabs/fastclick/issues/466 – Tim Perry Sep 23 '16 at 19:14