From 3b662fe295307e898637912c8236d09e03d92909 Mon Sep 17 00:00:00 2001 From: Gonzalo Ruiz de Villa Date: Tue, 2 Sep 2014 21:39:39 +0200 Subject: [PATCH 1/2] fix(input): $asyncValidators should allow $parsers After resolving an async validator, it should compare current viewValue against the value it had when the validator was started before reducing pendingCount. Before this commit the comparison was made between the modelValue when the validation started and the current viewValue. --- src/ng/directive/input.js | 10 +++++----- test/ng/directive/inputSpec.js | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 1793e570f2a3..4c8df325c2a3 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -1709,7 +1709,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ $animate.removeClass($element, PENDING_CLASS); }; - this.$$setPending = function(validationErrorKey, promise, currentValue) { + this.$$setPending = function(validationErrorKey, promise, modelValue, viewValue) { ctrl.$pending = ctrl.$pending || {}; if (angular.isUndefined(ctrl.$pending[validationErrorKey])) { ctrl.$pending[validationErrorKey] = true; @@ -1725,19 +1725,19 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ //Special-case for (undefined|null|false|NaN) values to avoid //having to compare each of them with each other - currentValue = currentValue || ''; + viewValue = viewValue || ''; promise.then(resolve(true), resolve(false)); function resolve(bool) { return function() { var value = ctrl.$viewValue || ''; - if (ctrl.$pending && ctrl.$pending[validationErrorKey] && currentValue === value) { + if (ctrl.$pending && ctrl.$pending[validationErrorKey] && viewValue === value) { pendingCount--; delete ctrl.$pending[validationErrorKey]; ctrl.$setValidity(validationErrorKey, bool); if (pendingCount === 0) { ctrl.$$clearPending(); - ctrl.$$updateValidModelValue(value); + ctrl.$$updateValidModelValue(modelValue); ctrl.$$writeModelToScope(); } } @@ -1947,7 +1947,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ throw $ngModelMinErr("$asyncValidators", "Expected asynchronous validator to return a promise but got '{0}' instead.", result); } - ctrl.$$setPending(validator, result, modelValue); + ctrl.$$setPending(validator, result, modelValue, viewValue); }); } diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index 3c5e6d437cb1..f4320293eab1 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -730,6 +730,31 @@ describe('NgModelController', function() { dealoc(element); })); + it('should re-evaluate the form validity state against a parsed view value', + inject(function($compile, $rootScope, $q) { + var element = $compile('
' + + '' + + '
')($rootScope); + var inputElm = element.find('input'); + + var formCtrl = $rootScope.myForm; + var curiousnumberCtrl = formCtrl.curiousnumber; + var curiousnumberDefer; + curiousnumberCtrl.$asyncValidators.isCurious = function() { + curiousnumberDefer = $q.defer(); + return curiousnumberDefer.promise; + }; + + curiousnumberCtrl.$setViewValue("22"); + $rootScope.$digest(); + expect(curiousnumberCtrl.$pending.isCurious).toBe(true); + + curiousnumberDefer.resolve(); + $rootScope.$digest(); + expect(curiousnumberCtrl.$pending).toBeUndefined(); + + dealoc(element); + })); }); }); From 36902e67b440ab7eab562e83443ae209e1baf7c1 Mon Sep 17 00:00:00 2001 From: Gonzalo Ruiz de Villa Date: Wed, 3 Sep 2014 10:07:23 +0200 Subject: [PATCH 2/2] perf(input): should not run validators in case view value is not re-rendered --- src/ng/directive/input.js | 2 +- test/ng/directive/inputSpec.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js index 4c8df325c2a3..ef2b192468db 100644 --- a/src/ng/directive/input.js +++ b/src/ng/directive/input.js @@ -2133,10 +2133,10 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ viewValue = formatters[idx](viewValue); } - ctrl.$$runValidators(modelValue, viewValue); if (ctrl.$viewValue !== viewValue) { ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; + ctrl.$$runValidators(modelValue, viewValue); ctrl.$render(); } } diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js index f4320293eab1..6216e875e642 100644 --- a/test/ng/directive/inputSpec.js +++ b/test/ng/directive/inputSpec.js @@ -667,6 +667,16 @@ describe('NgModelController', function() { expect(isObject(ctrl.$pending)).toBe(false); })); + it('should not run validators in case view value is not re-rendered', function() { + ctrl.$formatters.push(function(value) { + return 'nochange'; + }); + ctrl.$validators.spyValidator = jasmine.createSpy('spyValidator'); + scope.$apply('value = "first"'); + scope.$apply('value = "second"'); + expect(ctrl.$validators.spyValidator).toHaveBeenCalledOnce(); + }); + it('should re-evaluate the form validity state once the asynchronous promise has been delivered', inject(function($compile, $rootScope, $q) {