I want to use ng-pattern to further enforce the date format on an input field using Angular UI datepicker. The problem is when I do this it's always showing invalid.
<p class="input-group">
<input type="text" datepicker-popup="MM/dd/yyyy" is-open="opened" close-text="Close"
ng-model="someDate" class="form-control" placeholder="mm/dd/yyyy"
ng-pattern="/^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/" required />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open($event)">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</p>
If I apply the same ng-pattern to a plain input field (no datepicker), it works as expected.
There seems to be a conflict but I'm not sure what it is. Any ideas?
Update:
Here is a simple plunker to illustrate the issue as requested. After some further digging it does seem to be running the pattern against the underlying Date
object. When I use a custom directive that formats the date, however, it runs it against the actual input.
The only documentation I see for ng-pattern is a brief mention here under input. Is there anything else that maybe I'm missing?
As I mentioned in my comment, what is happening is that the datepicker
directive is changing the model value from a String
to a Date
object. When ngPattern
tried to validate the Date
object it will fail because the string value of a Date
is not going to match the pattern that you are using.
What you can do is create your own directive that hooks into the $parsers of the ngModelController to run your pattern check and then call $setValidity()
based on what the value is. $parsers
is actually built for this type of functionality where you want to run your own custom validation on a ngModel
value.
Following these bullets is a directive that will accomplish the functionality that you want. I want to explain the logic in the directive before I show you the code:
ngModel
in your directive definition so that you can access the ngModelController
datepicker
changes the model value from a String
to a Date
. Since datepicker
uses unshift
to add it's $parser function to the beginning of the $parsers
array, we also need to use unshift
to put our $parser before datepicker
's $parser.datepicker
directive will take any value that it can parse into a date and use that for the model. In order to make sure that only dates which match the pattern are used, I'm returning undefined
(as specified in the docs) for dates that don't match the pattern. I'm not doing this check for values that come in as a Date
object since that means that it was chosen using the datepicker
widget.Date
object since that means that it was chosen using the datepicker
widget and that means that it was a valid date.directive code
app.directive('awDatepickerPattern',function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope,elem,attrs,ngModelCtrl) {
var dRegex = new RegExp(attrs.awDatepickerPattern);
ngModelCtrl.$parsers.unshift(function(value) {
if (typeof value === 'string') {
var isValid = dRegex.test(value);
ngModelCtrl.$setValidity('date',isValid);
if (!isValid) {
return undefined;
}
}
return value;
});
}
};
});
html - make sure that you don't forget to remove the beginning and trailing slash in the regex definition:
aw-datepicker-pattern="^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$"
And here is an updated plunker with my code.
In case you didn't notice the root cause for why ng-pattern doesn't work when used with the datepicker
directive is because the datepicker
directive adds it's own ngModelController $parser to the beginning of the $parsers array by using unshift
, which changes the model value from a String
to a Date
.
It seems that an alternative solution to the other workarounds posted here is to upgrade to AngularJS 1.4.5, released 2015-08-28:
https://github.com/angular/angular.js/blob/master/CHANGELOG.md#145-permanent-internship-2015-08-28
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With