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