Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 2d6a0a1

Browse files
committed
fix(ngModelController): always use the most recent viewValue for validation
This fixes issues where a parser calls $setViewValue. This is a common strategy for manipulating the $viewValue while the user is entering data into an input field. When the $viewValue was changed inside the parser, the new viewValue would be committed, parsed and used for validation. The original parser however would run after that and pass the original (outdated) viewValue on to the validators, which could cause false positives, e.g. for minlength. Fixes #10126 Fixes #10299
1 parent 0c4f9fa commit 2d6a0a1

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

src/ng/directive/input.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -2196,11 +2196,15 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
21962196
var prevModelValue = ctrl.$modelValue;
21972197
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
21982198
ctrl.$$rawModelValue = modelValue;
2199+
21992200
if (allowInvalid) {
22002201
ctrl.$modelValue = modelValue;
22012202
writeToModelIfNeeded();
22022203
}
2203-
ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
2204+
2205+
// Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
2206+
// This can happen if e.g. $setViewValue is called from inside a parser
2207+
ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
22042208
if (!allowInvalid) {
22052209
// Note: Don't check ctrl.$valid here, as we could have
22062210
// external validators (e.g. calculated on the server),

test/ng/directive/inputSpec.js

+50
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,56 @@ describe('NgModelController', function() {
10661066

10671067
dealoc(element);
10681068
}));
1069+
1070+
it('should always use the most recent $viewValue for validation', function() {
1071+
ctrl.$parsers.push(function(value) {
1072+
if (value && value.substr(-1) === 'b') {
1073+
value = 'a';
1074+
ctrl.$setViewValue(value);
1075+
ctrl.$render();
1076+
}
1077+
1078+
return value;
1079+
});
1080+
1081+
ctrl.$validators.mock = function(modelValue) {
1082+
return true;
1083+
};
1084+
1085+
spyOn(ctrl.$validators, 'mock').andCallThrough();
1086+
1087+
ctrl.$setViewValue('ab');
1088+
1089+
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'a');
1090+
expect(ctrl.$validators.mock.calls.length).toEqual(2);
1091+
});
1092+
1093+
it('should validate even if the modelValue did not change', function() {
1094+
ctrl.$parsers.push(function(value) {
1095+
if (value && value.substr(-1) === 'b') {
1096+
value = 'a';
1097+
}
1098+
1099+
return value;
1100+
});
1101+
1102+
ctrl.$validators.mock = function(modelValue) {
1103+
return true;
1104+
};
1105+
1106+
spyOn(ctrl.$validators, 'mock').andCallThrough();
1107+
1108+
ctrl.$setViewValue('a');
1109+
1110+
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'a');
1111+
expect(ctrl.$validators.mock.calls.length).toEqual(1);
1112+
1113+
ctrl.$setViewValue('ab');
1114+
1115+
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'ab');
1116+
expect(ctrl.$validators.mock.calls.length).toEqual(2);
1117+
});
1118+
10691119
});
10701120
});
10711121

0 commit comments

Comments
 (0)