9

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?

12
  • 2
    Just a guess, but maybe ng-pattern is validating the model value and not the view value. When you use the datepicker in angular-ui, the model value is saved as a Date object, not as a String. Commented May 20, 2014 at 16:46
  • Do you use html2js in your building process? If you do, what version is it? Commented May 20, 2014 at 16:46
  • @JoseM I thought of that (the documentation is not very clear) but when I replicate on another input backed by a Date object it works, so it seems to be something with the datepicker. Commented May 20, 2014 at 17:11
  • @JoakimB I'm not familiar with html2js... what does it do? Commented May 20, 2014 at 17:13
  • 1
    @aw04 I looked at the plunker and I also looked at the code for datepicker-popup and I know what is happening. datepicker makes sure the model value is a Date object (as long as it can be parsed), but ng-pattern only works on strings. I was thinking of how to get your requirement to work but more importantly why do you need that ng-pattern validation? datepicker is already forcing it to be a valid date Commented May 20, 2014 at 22:40

3 Answers 3

21

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:

  • In order to add your own $parser you have to add a require for ngModel in your directive definition so that you can access the ngModelController
  • Because RegExp expects the pattern argument without the beginning and trailing slash, I removed them in the pattern directive argument
  • 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.
  • As you mentioned, the 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.
  • As I just alluded to in the previous bullet, I'm not doing the validity check if the value is already a 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.

So why again didn't ng-pattern work?

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.

Sign up to request clarification or add additional context in comments.

4 Comments

A note to anyone else, I had to set a high priority on this directive to get it to work correctly in my environment
The same as aw04. Without priority, I always received Date object in the unshift function, so my string parser was never executed.
Just noticed one issue: if I enter invalid date manually and after that I pick a valid date from datepicker dropdown, then validation still fails. Obviously, picking a valid value does not reset the model state. Maybe this has something to do with changes by Rory G. Anyway, I added a line ngModelCtrl.$setValidity('datep',true) at the very beginning of unshift to reset the state to valid, and this seems to be working fine now.
if any test cases are written for this directive, please share it.. it will be helpful..
4

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

Comments

3

The above directive plunker only works when the date is required. I fixed it by modifying the directive as follows:

ngModelCtrl.$setValidity('datep',isValid);

Since the date picker runs after this directive it sets 'date' back to valid.

3 Comments

This probably should have been a comment on the other answer, but I guess new users can't do that.
Great, thanks. How did you came to the 'datep' value? Is it documented anywhere or did you dig it somewhere in Date Picker's source code?
It can be anything eg 'datex' as long as it is not 'date'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.