diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 6a974d9dae02..c280b7c25f47 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -407,7 +407,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { }); } - var listener = function() { + var listener = function(ev) { if (composing) return; var value = element.val(); @@ -419,9 +419,17 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { } if (ctrl.$viewValue !== value) { - scope.$apply(function() { + // If an event was performed natively, jQuery sets the isTrigger property. + // When triggering event manually, the field is not present. Manually + // triggered events are performed synchronously which causes the "$digest + // already in progress" error. + if (ev && ev.isTrigger) { ctrl.$setViewValue(value); - }); + } else { + scope.$apply(function() { + ctrl.$setViewValue(value); + }); + } } }; diff --git a/src/ngScenario/Scenario.js b/src/ngScenario/Scenario.js index 9a30d8a9ace9..f323e01d0f5a 100644 --- a/src/ngScenario/Scenario.js +++ b/src/ngScenario/Scenario.js @@ -239,7 +239,8 @@ function callerFile(offset) { * To work around this we instead use our own handler that fires a real event. */ (function(fn){ - var parentTrigger = fn.trigger; + // We need a handle to the original trigger function for input tests. + var parentTrigger = fn._originalTrigger = fn.trigger; fn.trigger = function(type) { if (/(click|change|keydown|blur|input|mousedown|mouseup)/.test(type)) { var processDefaults = []; diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 48319cfaf2ff..c9a345cdd38f 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -533,6 +533,23 @@ describe('input', function() { 'event so that form auto complete works',function() { assertBrowserSupportsChangeEvent(true); }); + + if (!_jqLiteMode) { + it('should not cause the double $digest when triggering an event using jQuery', function() { + $sniffer.hasEvent = function(eventName) { + return eventName !== 'input'; + }; + + compileInput(''); + + scope.field = 'fake field'; + scope.$watch('field', function() { + // We need to use _originalTrigger since trigger is modified by Angular Scenario. + inputElm._originalTrigger('change'); + }); + scope.$apply(); + }); + } }); describe('"paste" and "cut" events', function() {