From 0bc9a02f8c572f850d377d638e08319621053607 Mon Sep 17 00:00:00 2001 From: Mark Gardner Date: Wed, 25 May 2016 13:02:19 -0600 Subject: [PATCH] fix(ngEventDirs): check for $rootScope.$$phase in event handler and don't $apply if already in $digest Digest cycle already in progress error can inadvertently be caused when triggering an element's click event while within an active digest cycle. This is due to the ngEventsDirs event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase. Checking the phase and calling the function immediately if within an active digest cycle will prevent the problem without reducing current functionality. Closes #14673 --- src/ng/directive/ngEventDirs.js | 6 ++-- test/ng/directive/ngEventDirsSpec.js | 42 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/ng/directive/ngEventDirs.js b/src/ng/directive/ngEventDirs.js index c14b173a2b48..75edf369efd6 100644 --- a/src/ng/directive/ngEventDirs.js +++ b/src/ng/directive/ngEventDirs.js @@ -63,10 +63,12 @@ forEach( var callback = function() { fn(scope, {$event:event}); }; - if (forceAsyncEvents[eventName] && $rootScope.$$phase) { + if (!$rootScope.$$phase) { + scope.$apply(callback); + } else if (forceAsyncEvents[eventName]) { scope.$evalAsync(callback); } else { - scope.$apply(callback); + callback(); } }); }; diff --git a/test/ng/directive/ngEventDirsSpec.js b/test/ng/directive/ngEventDirsSpec.js index e2c2745f840e..df85fd902cd1 100644 --- a/test/ng/directive/ngEventDirsSpec.js +++ b/test/ng/directive/ngEventDirsSpec.js @@ -150,4 +150,46 @@ describe('event directives', function() { })); }); + + describe('click', function() { + + it('should call the listener synchronously if inside of $apply', + inject(function($rootScope, $compile) { + var watchedVal; + + element = $compile('')($rootScope); + $rootScope.$watch('value', function(newValue) { + watchedVal = newValue; + }); + $rootScope.click = jasmine.createSpy('click').and.callFake(function() { + $rootScope.value = 'newValue'; + }); + + $rootScope.$apply(function() { + element.triggerHandler('click'); + }); + + expect($rootScope.click).toHaveBeenCalledOnce(); + expect(watchedVal).toEqual('newValue'); + })); + + it('should call the listener synchronously if outside of $apply', + inject(function($rootScope, $compile) { + var watchedVal; + + element = $compile('')($rootScope); + $rootScope.$watch('value', function(newValue) { + watchedVal = newValue; + }); + $rootScope.click = jasmine.createSpy('click').and.callFake(function() { + $rootScope.value = 'newValue'; + }); + + element.triggerHandler('click'); + + expect($rootScope.click).toHaveBeenCalledOnce(); + expect(watchedVal).toEqual('newValue'); + })); + + }); });