Skip to content

Commit f3cb274

Browse files
shahatapetebacondarwin
authored andcommitted
fix(ngModel): test & update correct model when running $validate
If `$validate` is invoked when the model is already invalid, `$validate` should pass `$$invalidModelValue` to the validators, not `$modelValue`. Moreover, if `$validate` is invoked and it is found that the invalid model has become valid, this previously invalid model should be assigned to `$modelValue`. Lastly, if `$validate` is invoked and it is found that the model has become invalid, the previously valid model should be assigned to `$$invalidModelValue`. Closes angular#7836 Closes angular#7837
1 parent 1a9cb0a commit f3cb274

File tree

2 files changed

+102
-31
lines changed

2 files changed

+102
-31
lines changed

src/ng/directive/input.js

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,13 +1781,24 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
17811781
* Runs each of the registered validations set on the $validators object.
17821782
*/
17831783
this.$validate = function() {
1784-
this.$$runValidators(ctrl.$modelValue, ctrl.$viewValue);
1784+
// ignore $validate before model initialized
1785+
if (ctrl.$modelValue !== ctrl.$modelValue) {
1786+
return;
1787+
}
1788+
1789+
var prev = ctrl.$modelValue;
1790+
ctrl.$$runValidators(ctrl.$$invalidModelValue || ctrl.$modelValue, ctrl.$viewValue);
1791+
if (prev !== ctrl.$modelValue) {
1792+
ctrl.$$writeModelToScope();
1793+
}
17851794
};
17861795

17871796
this.$$runValidators = function(modelValue, viewValue) {
17881797
forEach(ctrl.$validators, function(fn, name) {
17891798
ctrl.$setValidity(name, fn(modelValue, viewValue));
17901799
});
1800+
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
1801+
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
17911802
};
17921803

17931804
/**
@@ -1826,22 +1837,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
18261837

18271838
if (ctrl.$modelValue !== modelValue &&
18281839
(isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) {
1829-
18301840
ctrl.$$runValidators(modelValue, viewValue);
1831-
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
1832-
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
1833-
1834-
ngModelSet($scope, ctrl.$modelValue);
1835-
forEach(ctrl.$viewChangeListeners, function(listener) {
1836-
try {
1837-
listener();
1838-
} catch(e) {
1839-
$exceptionHandler(e);
1840-
}
1841-
});
1841+
ctrl.$$writeModelToScope();
18421842
}
18431843
};
18441844

1845+
this.$$writeModelToScope = function() {
1846+
ngModelSet($scope, ctrl.$modelValue);
1847+
forEach(ctrl.$viewChangeListeners, function(listener) {
1848+
try {
1849+
listener();
1850+
} catch(e) {
1851+
$exceptionHandler(e);
1852+
}
1853+
});
1854+
};
1855+
18451856
/**
18461857
* @ngdoc method
18471858
* @name ngModel.NgModelController#$setViewValue
@@ -1920,8 +1931,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
19201931
}
19211932

19221933
ctrl.$$runValidators(modelValue, viewValue);
1923-
ctrl.$modelValue = ctrl.$valid ? modelValue : undefined;
1924-
ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue;
19251934

19261935
if (ctrl.$viewValue !== viewValue) {
19271936
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;

test/ng/directive/inputSpec.js

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -294,27 +294,13 @@ describe('NgModelController', function() {
294294
};
295295

296296
ctrl.$modelValue = 'test';
297+
ctrl.$$invalidModelValue = undefined;
297298
ctrl.$validate();
298299

299300
expect(ctrl.$valid).toBe(false);
300301

301302
ctrl.$modelValue = 'TEST';
302-
ctrl.$validate();
303-
304-
expect(ctrl.$valid).toBe(true);
305-
});
306-
307-
it('should perform validations when $validate() is called', function() {
308-
ctrl.$validators.uppercase = function(value) {
309-
return (/^[A-Z]+$/).test(value);
310-
};
311-
312-
ctrl.$modelValue = 'test';
313-
ctrl.$validate();
314-
315-
expect(ctrl.$valid).toBe(false);
316-
317-
ctrl.$modelValue = 'TEST';
303+
ctrl.$$invalidModelValue = undefined;
318304
ctrl.$validate();
319305

320306
expect(ctrl.$valid).toBe(true);
@@ -403,6 +389,7 @@ describe('NgModelController', function() {
403389
};
404390
};
405391

392+
ctrl.$modelValue = undefined;
406393
ctrl.$validators.a = curry(true);
407394
ctrl.$validators.b = curry(true);
408395
ctrl.$validators.c = curry(false);
@@ -423,6 +410,7 @@ describe('NgModelController', function() {
423410
};
424411
};
425412

413+
ctrl.$modelValue = undefined;
426414
ctrl.$validators.unique = curry(false);
427415
ctrl.$validators.tooLong = curry(false);
428416
ctrl.$validators.notNumeric = curry(true);
@@ -1489,6 +1477,80 @@ describe('input', function() {
14891477
expect(inputElm).toBeValid();
14901478
expect(scope.form.input.$error.maxlength).not.toBe(true);
14911479
});
1480+
1481+
it('should assign the correct model after an observed validator became valid', function() {
1482+
compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
1483+
1484+
scope.$apply(function() {
1485+
scope.max = 1;
1486+
});
1487+
changeInputValueTo('12345');
1488+
expect(scope.value).toBeUndefined();
1489+
1490+
scope.$apply(function() {
1491+
scope.max = 6;
1492+
});
1493+
expect(scope.value).toBe('12345');
1494+
});
1495+
1496+
it('should assign the correct model after an observed validator became invalid', function() {
1497+
compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
1498+
1499+
scope.$apply(function() {
1500+
scope.max = 6;
1501+
});
1502+
changeInputValueTo('12345');
1503+
expect(scope.value).toBe('12345');
1504+
1505+
scope.$apply(function() {
1506+
scope.max = 1;
1507+
});
1508+
expect(scope.value).toBeUndefined();
1509+
});
1510+
1511+
it('should leave the value as invalid if observed maxlength changed, but is still invalid', function() {
1512+
compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
1513+
scope.$apply(function() {
1514+
scope.max = 1;
1515+
});
1516+
1517+
changeInputValueTo('12345');
1518+
expect(inputElm).toBeInvalid();
1519+
expect(scope.form.input.$error.maxlength).toBe(true);
1520+
expect(scope.value).toBeUndefined();
1521+
1522+
scope.$apply(function() {
1523+
scope.max = 3;
1524+
});
1525+
1526+
expect(inputElm).toBeInvalid();
1527+
expect(scope.form.input.$error.maxlength).toBe(true);
1528+
expect(scope.value).toBeUndefined();
1529+
});
1530+
1531+
it('should not notify if observed maxlength changed, but is still invalid', function() {
1532+
compileInput('<input type="text" name="input" ng-model="value" ng-change="ngChangeSpy()" ' +
1533+
'maxlength="{{ max }}" />');
1534+
1535+
scope.$apply(function() {
1536+
scope.max = 1;
1537+
});
1538+
changeInputValueTo('12345');
1539+
1540+
scope.ngChangeSpy = jasmine.createSpy();
1541+
scope.$apply(function() {
1542+
scope.max = 3;
1543+
});
1544+
1545+
expect(scope.ngChangeSpy).not.toHaveBeenCalled();
1546+
});
1547+
1548+
it('should leave the model untouched when validating before model initialization', function() {
1549+
scope.value = '12345';
1550+
compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
1551+
expect(scope.value).toBe('12345');
1552+
});
1553+
14921554
});
14931555

14941556

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy