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

Commit 1be9bb9

Browse files
committed
fix(NgModel): ensure pattern and ngPattern use the same validator
When the pattern and ng-pattern attributes are used with an input element containing a ngModel directive then they should both use the same validator and the validation errors of the model should be placed on model.$error.pattern. BREAKING CHANGE: If an expression is used on ng-pattern (such as `ng-pattern="exp"`) or on the pattern attribute (something like on `pattern="{{ exp }}"`) and the expression itself evaluates to a string then the validator will not parse the string as a literal regular expression object (a value like `/abc/i`). Instead, the entire string will be created as the regular expression to test against. This means that any expression flags will not be placed on the RegExp object. To get around this limitation, use a regular expression object as the value for the expression. //before $scope.exp = '/abc/i'; //after $scope.exp = /abc/i;
1 parent 26d91b6 commit 1be9bb9

File tree

3 files changed

+85
-29
lines changed

3 files changed

+85
-29
lines changed

src/AngularPublic.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
ngModelDirective,
4444
ngListDirective,
4545
ngChangeDirective,
46+
patternDirective,
47+
patternDirective,
4648
requiredDirective,
4749
requiredDirective,
4850
minlengthDirective,
@@ -186,12 +188,14 @@ function publishExternalAPI(angular){
186188
ngModel: ngModelDirective,
187189
ngList: ngListDirective,
188190
ngChange: ngChangeDirective,
191+
pattern: patternDirective,
192+
ngPattern: patternDirective,
189193
required: requiredDirective,
190194
ngRequired: requiredDirective,
191-
ngMinlength: minlengthDirective,
192195
minlength: minlengthDirective,
193-
ngMaxlength: maxlengthDirective,
196+
ngMinlength: minlengthDirective,
194197
maxlength: maxlengthDirective,
198+
ngMaxlength: maxlengthDirective,
195199
ngValue: ngValueDirective,
196200
ngModelOptions: ngModelOptionsDirective
197201
}).

src/ng/directive/input.js

+30-25
Original file line numberDiff line numberDiff line change
@@ -975,31 +975,6 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
975975
ctrl.$render = function() {
976976
element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
977977
};
978-
979-
// pattern validator
980-
if (attr.ngPattern) {
981-
var regexp, patternExp = attr.ngPattern;
982-
attr.$observe('pattern', function(regex) {
983-
if(isString(regex)) {
984-
var match = regex.match(REGEX_STRING_REGEXP);
985-
if(match) {
986-
regex = new RegExp(match[1], match[2]);
987-
}
988-
}
989-
990-
if (regex && !regex.test) {
991-
throw minErr('ngPattern')('noregexp',
992-
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
993-
regex, startingTag(element));
994-
}
995-
996-
regexp = regex || undefined;
997-
});
998-
999-
ctrl.$validators.pattern = function(value) {
1000-
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
1001-
};
1002-
}
1003978
}
1004979

1005980
function weekParser(isoWeek) {
@@ -2167,6 +2142,36 @@ var requiredDirective = function() {
21672142
};
21682143

21692144

2145+
var patternDirective = function() {
2146+
return {
2147+
require: '?ngModel',
2148+
link: function(scope, elm, attr, ctrl) {
2149+
if (!ctrl) return;
2150+
2151+
var regexp, patternExp = attr.ngPattern || attr.pattern;
2152+
attr.$observe('pattern', function(regex) {
2153+
if(isString(regex) && regex.length > 0) {
2154+
regex = new RegExp(regex);
2155+
}
2156+
2157+
if (regex && !regex.test) {
2158+
throw minErr('ngPattern')('noregexp',
2159+
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
2160+
regex, startingTag(elm));
2161+
}
2162+
2163+
regexp = regex || undefined;
2164+
ctrl.$validate();
2165+
});
2166+
2167+
ctrl.$validators.pattern = function(value) {
2168+
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
2169+
};
2170+
}
2171+
};
2172+
};
2173+
2174+
21702175
var maxlengthDirective = function() {
21712176
return {
21722177
require: '?ngModel',

test/ng/directive/inputSpec.js

+49-2
Original file line numberDiff line numberDiff line change
@@ -1331,12 +1331,59 @@ describe('input', function() {
13311331
expect(inputElm).toBeInvalid();
13321332
});
13331333

1334+
it('should perform validations when the ngPattern scope value changes', function() {
1335+
scope.regexp = /^[a-z]+$/;
1336+
compileInput('<input type="text" ng-model="value" ng-pattern="regexp" />');
1337+
1338+
changeInputValueTo('abcdef');
1339+
expect(inputElm).toBeValid();
1340+
1341+
changeInputValueTo('123');
1342+
expect(inputElm).toBeInvalid();
1343+
1344+
scope.$apply(function() {
1345+
scope.regexp = /^\d+$/;
1346+
});
1347+
1348+
expect(inputElm).toBeValid();
1349+
1350+
changeInputValueTo('abcdef');
1351+
expect(inputElm).toBeInvalid();
1352+
1353+
scope.$apply(function() {
1354+
scope.regexp = '';
1355+
});
1356+
1357+
expect(inputElm).toBeValid();
1358+
});
1359+
1360+
it('should register "pattern" with the model validations when the pattern attribute is used', function() {
1361+
compileInput('<input type="text" name="input" ng-model="value" pattern="^\\d+$" />');
1362+
1363+
changeInputValueTo('abcd');
1364+
expect(inputElm).toBeInvalid();
1365+
expect(scope.form.input.$error.pattern).toBe(true);
1366+
1367+
changeInputValueTo('12345');
1368+
expect(inputElm).toBeValid();
1369+
expect(scope.form.input.$error.pattern).not.toBe(true);
1370+
});
1371+
1372+
it('should not throw an error when scope pattern can\'t be found', function() {
1373+
expect(function() {
1374+
compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
1375+
scope.$apply(function() {
1376+
scope.foo = 'bar';
1377+
});
1378+
}).not.toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
1379+
});
13341380

1335-
it('should throw an error when scope pattern is invalid', function() {
1381+
it('should throw an error when the scope pattern is not a regular expression', function() {
13361382
expect(function() {
13371383
compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
13381384
scope.$apply(function() {
1339-
scope.fooRegexp = '/...';
1385+
scope.fooRegexp = {};
1386+
scope.foo = 'bar';
13401387
});
13411388
}).toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
13421389
});

0 commit comments

Comments
 (0)