diff --git a/src/ng/directive/ngEventDirs.js b/src/ng/directive/ngEventDirs.js index c14b173a2b48..3a8f3d034a60 100644 --- a/src/ng/directive/ngEventDirs.js +++ b/src/ng/directive/ngEventDirs.js @@ -60,8 +60,12 @@ forEach( var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); return function ngEventHandler(scope, element) { element.on(eventName, function(event) { - var callback = function() { - fn(scope, {$event:event}); + var callback = function(scope, locals) { + fn(scope, { + $event:event, + $abortApply: locals && locals.$abortApply, + $partialDigest: locals && locals.$partialDigest + }); }; if (forceAsyncEvents[eventName] && $rootScope.$$phase) { scope.$evalAsync(callback); diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 1ee04ed15770..ace548cef3b3 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -1022,18 +1022,31 @@ function $RootScopeProvider() { * @returns {*} The result of evaluating the expression. */ $apply: function(expr) { + var abortApply = false; + var scopeForDigest = $rootScope; + var currentScope = this; + try { beginPhase('$apply'); - return this.$eval(expr); + return this.$eval(expr, { + $abortApply: function() { + abortApply = true; + }, + $partialDigest: function(scope) { + scopeForDigest = scope || currentScope; + } + }); } catch (e) { $exceptionHandler(e); } finally { clearPhase(); - try { - $rootScope.$digest(); - } catch (e) { - $exceptionHandler(e); - throw e; + if (!abortApply) { + try { + scopeForDigest.$digest(); + } catch (e) { + $exceptionHandler(e); + throw e; + } } } }, diff --git a/test/ng/directive/ngClickSpec.js b/test/ng/directive/ngClickSpec.js index 3e997c462b20..a8f3acf163af 100644 --- a/test/ng/directive/ngClickSpec.js +++ b/test/ng/directive/ngClickSpec.js @@ -23,4 +23,24 @@ describe('ngClick', function() { browserTrigger(element, 'click'); expect($rootScope.event).toBeDefined(); })); + + it('should abort apply if abort argument is invoked', inject(function($rootScope, $compile) { + element = $compile('