21

I am new to Angular. I am using angular 4 reactive forms and figured out how to perform custom validations. Following is my implementation for numeric

function numberValidator(c: AbstractControl): { [key: string]: boolean } | null {
    if (c.pristine) {
        return null;
    }
    if (c.value.match(/.*[^0-9].*/)) {
        return { 'numeric': true };
    }
    return null;
}

 phoneControl: ['', [Validators.required, Validators.minLength(10), Validators.maxLength(10), numberValidator]],

I am trying to find out how to perform currency (with or without two decimal places) and most importantly Date field.

Forgive me if this was answered elsewhere but I cannot find any samples for angular(4)

Thanks for your time

Kamil Naja
  • 6,267
  • 6
  • 33
  • 47
Vinod
  • 343
  • 1
  • 4
  • 14

9 Answers9

24

What kind of date validation do you need? Just that the value is a valid date? If you set the type="date" on the input element the browser will ensure that only a valid date is entered.

Same for your shown number validator and for any currency validator. You should be able to set the type="number" on the input element and you won't need a validator.

If you still do want to perform your own validation, you can use regular expressions just as in your example.

Just look up regular expressions for currency and date. For the date, something like this: Javascript - Regex to validate date format

DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • 1
    Thanks for the reply. Does setting type="date" works in all browsers or just the ones that supports html 5 . To be on safer side I would like to do my own validation on component side. So you are recommending regex for custom validation? Thanks – Vinod Oct 06 '17 at 16:22
  • That is what you are doing in your example here: `(c.value.match(/.*[^0-9].*/)) ` so I assumed you wanted something similar? And you can determine which browsers support specific features using this: https://caniuse.com/#search=type%3D%22date%22 – DeborahK Oct 06 '17 at 16:49
  • Thanks for your reply. – Vinod Oct 06 '17 at 17:33
  • If you could mark this as the answer we can close this question. Thanks! – DeborahK Oct 06 '17 at 18:53
  • 1
    input type = "date" is not supported in IE11 and earlier versions! – Steffi Keran Rani J Apr 27 '18 at 06:39
  • This is not quite true (for dates). As you're typing in the date, as soon as there's a date with the correct number of digits, it has a "valid" value of true. So, 25-12-0001 is a perfectly valid date. I mean... okay... yes, it is.... but do you really know of any occassions when a user really does mean the year 1 ? – Mike Gledhill Nov 23 '19 at 11:27
  • As you said, 25-12-001 is a *valid* date. It may not be what was intended ... but that is why we also implement validation/business rules. For some app, 22/11/2019 is also not a valid date because it is in the past and it only allows future dates. AFAIK, there is no way to specify a date range as part of basic html validation. You'd need to implement some validation logic to ensure the date is in a range you are expecting. – DeborahK Nov 23 '19 at 18:51
  • type="date" does not work well on Android devices. Had to change it because users were scrolling through months to change the year, instead of just clicking the year. They didn't realize the year is a drop-down. – Jonathas Sucupira Dec 30 '20 at 19:56
15

This is another option to using Custom validators

import { FormControl } from '@angular/forms';

export class DateValidator {

   static ptDate(control: FormControl): { [key: string]: any } {
       let ptDatePattern =  /^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/g;

       if (!control.value.match(ptDatePattern))
           return { "ptDate": true };

       return null;
   }

   static usDate(control: FormControl): { [key: string]: any } {
       let usDatePattern = /^02\/(?:[01]\d|2\d)\/(?:19|20)(?:0[048]|[13579][26]|[2468][048])|(?:0[13578]|10|12)\/(?:[0-2]\d|3[01])\/(?:19|20)\d{2}|(?:0[469]|11)\/(?:[0-2]\d|30)\/(?:19|20)\d{2}|02\/(?:[0-1]\d|2[0-8])\/(?:19|20)\d{2}$/;

       if (!control.value.match(usDatePattern))
           return { "usDate": true };

       return null;
   }
}

and use it this way for "dd/mm/yyyy" format:

this.formDetail = this.formBuilder.group({
   date: ['', DateValidator.ptDate],
});

and use it this way for "mm/dd/yyyy" format:

this.formDetail = this.formBuilder.group({
   date: ['', DateValidator.usDate],
});

I hope this help!

EQuadrado
  • 221
  • 4
  • 4
  • Where do we add this in app.module.ts, etc? – MaxRocket Jun 10 '19 at 12:23
  • @MaxRocket validators are just functions. You don't add them to your module. Simply import them: `import { checkDateMethod } from './check-date.validator';` and use them directly without calling this function: `new FormControl(null, { asyncValidators: [checkDateMethod] })` – Aleksey Solovey Oct 02 '20 at 15:52
  • This solution worked well for me, the only change I made was to check for null. like ``if (control.value && !control.value.match(ptDatePattern)) return { "ptDate": true };``. Otherwise if initiated the field like: ``date: [null, [Validators.required, DateValidator.usDate]]`` it would throw an null exception right away. – Jonathas Sucupira Jan 05 '21 at 00:45
5

Created a custom validator to handle formats MM/DD/YYYY and MMDDYYYY

function dateValidator(c: AbstractControl): { [key: string]: boolean } | null {
    if (c.pristine) {
        return null;
    }
    if ((c.value !== undefined && c.value !== '' && c.value != null)) {

        var month = null;
        var day = null;
        var year = null;
        var currentTaxYear = Number(new Date().getFullYear() - 1);
        if (c.value.indexOf('/') > -1) {
            var res = c.value.split("/");           
            if (res.length > 1) {
                month = res[0];
                day = res[1]
                year = res[2];
            }                              
        }
        else {
            if (c.value.length == 8) {
                month = c.value.substring(0, 2);
                day = c.value.substring(2, 4);
                year = c.value.substring(4, 8);
            }            
        }
        if (isNaN(month) || isNaN(day) || isNaN(year)) {
            return { 'dateInvalid': true };
        } 
        month = Number(month);
        day = Number(day);
        year = Number(year);
        if (month < 1 || month > 12) { // check month range
            return { 'dateInvalid': true };
        }
        if (day < 1 || day > 31) {
            return { 'dateInvalid': true };
        }
        if ((month === 4 || month === 6 || month === 9 || month === 11) && day === 31) {
            return { 'dateInvalid': true };
        }
        if (month == 2) { // check for february 29th
            var isleap = (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0));
            if (day > 29 || (day === 29 && !isleap)) {
                return { 'dateInvalid': true };
            }
        }
        if (year !== currentTaxYear) {
            return { 'dateYearGreaterThanTaxYear': true };
        }
    }
    return null;
}
Vinod
  • 343
  • 1
  • 4
  • 14
5

If you are using reactive forms, you can write a custom validator,see below

dateValidator(c: AbstractControl): { [key: string]: boolean } {
    let value = c.value;
    if (value && typeof value === "string") {
      let match = value.match(/^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/);
      if (!match) {
        return { 'dateInvalid': true };
      } else if (match && match[0] !== value) {
        return { 'dateInvalid': true };
      }
    }
    return null;
  }

While creating control, you need to add this validator along with your other validators as shown below,

const group = this.fb.group({
    DateType: ['', [Validators.required, this.dateValidator]],
  })
3

This solution worked for me and the followings are satisfied


  1. startDate < endDate
  2. startDate >= today
  3. endDate >= today

app.component.html

<form [formGroup]="addContract>
 <div class="form-group">
              <label>Start Date *</label>
              <input type="date" name="startDate" formControlName="startDate" 
               class="form-control" placeholder="Arrival *"
               [ngClass]="{ 'is-invalid': submitted && f.startDate.errors}"/>
              <div *ngIf="f.startDate.touched">
                <small *ngIf="f.startDate.errors?.required"                    
                 class="text-danger">Required</small>
                <small *ngIf="addContract.errors?.invaineFrom" class="text-danger">  
                 Select a date Over today</small>
              </div>
    </div>

<div class="form-group">
              <label>End Date*</label>
              <input type="date" name="endDate" formControlName="endDate"
              class="form-control" placeholder="Departure *"
              [class.is-invalid]="endDate.invalid && endDate.touched"/>

              <div *ngIf="f.endDate.touched">
                <small *ngIf="endDate.errors?.required && 
                 endDate.touched ">Required</small>
                <small *ngIf="addContract.errors?.invaineTo" class="text-danger">    
                 choose a date over today</small>
                <small *ngIf="addContract.errors?.dates" class="text-danger">
                End Date should be grater than start Date</small>
              </div>
              </div>
            
           
 </div>
</form>

app.component.ts

import { DateValidator} from '../shared/date-validator';


export class AppComponent implements OnInit {
  addContract: FormGroup;
  ngOnInit() {
    this.addContract = this.fb.group({
      startDate: ['', [Validators.required]],
      endDate: ['', [Validators.required]]
    }, {validator: DateValidator});


  get f() { return this.addContract.controls; }

  get endDate() {
    return this.addContract.get('endDate');
  }
  get startDate() {
    return this.addContract.get('startDate');
  }

  }

date-validator.ts

import {AbstractControl} from '@angular/forms';

export function DateValidator(control: AbstractControl): { [ key: string]: boolean} | null { 



let from = control.get('startDate');
let to = control.get('endDate');
let c= new Date();
if(new Date(from.value)< c )
{
    return {
        invaineFrom:true
    }
}
if(new Date(to.value) < c )
{
    return {
        invaineTo:true
    }
}
if (from.value > to.value ) {
    return {
      dates: true
    };
  }
  return {};
}

Dharman
  • 30,962
  • 25
  • 85
  • 135
Niroshan Ratnayake
  • 3,433
  • 3
  • 20
  • 18
1

If you're using Angular Material, you can use the MatDatepicker integration with Moment.js to validate a custom date format using FormControl as shown here:

https://material.angular.io/components/datepicker/overview#customizing-the-parse-and-display-formats

HTML:

<input matInput [matDatepicker]="dp" placeholder="Verbose datepicker" [formControl]="date">

TS:

export const MY_FORMATS = {
  parse: {
    dateInput: 'LL',
  },
  display: {
    dateInput: 'LL',
    monthYearLabel: 'MMM YYYY',
    dateA11yLabel: 'LL',
    monthYearA11yLabel: 'MMMM YYYY',
  },
};

@Component({
  selector: 'datepicker-formats-example',
  templateUrl: 'datepicker-formats-example.html',
  styleUrls: ['datepicker-formats-example.css'],
  providers: [
    {provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE]},
    {provide: MAT_DATE_FORMATS, useValue: MY_FORMATS},
  ],
})
export class DatepickerFormatsExample {
  date = new FormControl(moment());
}
java-addict301
  • 3,220
  • 2
  • 25
  • 37
1

Here is (imo) a better date validation function:

function date(c: AbstractControl): { [key: string]: boolean } {
   let value = new Date(c.value);
   return isNaN(value.getTime()) || value <= new Date('01/01/1753') ? {'dateInvalid': true} : undefined;
}
Zach Wymer
  • 540
  • 9
  • 11
0

This is my solution:

import {AbstractControl} from "@angular/forms";

export class MyValidators {

  // validate MM/DD/YYYY
  static date(c: AbstractControl): { [key: string]: boolean } {
    let value = c.value;
    if (value && typeof value === "string") {
      let match = value.match(/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4})$/);
      if (!match) {
        return {'dateInvalid': true};
      }
      let date = new Date(`${match[3]}-${match[1]}-${match[2]}`);
      if (isNaN(date.getTime())) {
        return {'dateInvalid': true};
      }
    }
    return null;
  }

}
user1119279
  • 331
  • 3
  • 9
0

Angular mat-datepicker adds matDatepickerParse to a form control error object if it can't parse an input string

Yura
  • 1,733
  • 1
  • 20
  • 19