angularvalidationangular-materialangular-reactive-formsangular-forms

How to customize date validation error messages in Angular?


I am working on an Angular form where I need to validate a date input field. Currently, if I enter a non-date text like "Date" in the field, I receive the error message "Date is required". However, I want the error message to be "Invalid date format. Please use MM/DD/YYYY." instead.

How can I modify my code so that the error message is "Invalid date format. Please use MM/DD/YYYY." instead of "Date is required" when the input is not a valid date format?

Additionally, I need both error messages ("Date is required" and "Invalid date format. Please use MM/DD/YYYY.") but only one should be displayed at a time depending on the specific validation error.

Stackblitz Demo

task-view.component.ts

export class TaskFormComponent {
  taskForm!: FormGroup;
  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.taskForm = this.fb.group({
      dueTo: new FormControl('', [
        Validators.required,
        this.dateFormatValidator(),
      ]),
    });
  }

  public onSubmit() {}

  private dateFormatValidator(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      if (!control.value) {
        return null;
      }
      const validFormat = /^\d{1,2}\/\d{1,2}\/\d{4}$/;
      return validFormat.test(control.value)
        ? null
        : { invalidDateFormat: true };
    };
  }
}

task-view.component.html

<div mat-dialog-title>Create Task</div>

<mat-dialog-content>
  <form [formGroup]="taskForm">
    <mat-form-field>
      <mat-label>Choose a due date</mat-label>
      <input matInput formControlName="dueTo" [matDatepicker]="picker" />
      <mat-datepicker-toggle matIconSuffix [for]="picker">
        <mat-icon color="primary" matDatepickerToggleIcon
          >calendar_month</mat-icon
        >
      </mat-datepicker-toggle>
      <mat-datepicker #picker></mat-datepicker>
      @if (taskForm.get('dueTo')?.touched) {
      <mat-error>
        @switch (true) { @case (taskForm.get('dueTo')?.hasError('required'))
        {Date is required } @case
        (taskForm.get('dueTo')?.hasError('invalidDateFormat')) { Invalid date
        format. Please use MM/DD/YYYY. } }
      </mat-error>
      }
    </mat-form-field>
  </form>
</mat-dialog-content>

<mat-dialog-actions>
  <button
    (click)="onSubmit()"
    [disabled]="!taskForm.valid"
    mat-raised-button
    color="primary"
    type="submit"
  >
    Create
  </button>
</mat-dialog-actions>

Solution

  • It seems angular material has it's own error (matDatepickerParse) that shows when invalid input is present you can use this and show the errors. You do not need the custom validator.

      <mat-error>
        @switch (true) { 
          @case (taskForm.get('dueTo')?.hasError('matDatepickerParse')) { 
            Invalid date format. Please use MM/DD/YYYY. 
          } @case (taskForm.get('dueTo')?.hasError('required')) { 
            Date is required 
          } 
        }
      </mat-error>
    

    Full Code:

    HTML:

    <div mat-dialog-title>Create Task</div>
    
    <mat-dialog-content>
      <form [formGroup]="taskForm">
        <mat-form-field>
          <mat-label>Choose a due date</mat-label>
          <input matInput formControlName="dueTo" [matDatepicker]="picker" />
          <mat-datepicker-toggle matIconSuffix [for]="picker">
            <mat-icon color="primary" matDatepickerToggleIcon
              >calendar_month</mat-icon
            >
          </mat-datepicker-toggle>
          <mat-datepicker #picker></mat-datepicker>
          @if (taskForm.get('dueTo')?.touched) {
          <mat-error>
            @switch (true) { @case
            (taskForm.get('dueTo')?.hasError('matDatepickerParse')) { Invalid date
            format. Please use MM/DD/YYYY. } @case
            (taskForm.get('dueTo')?.hasError('required')) { Date is required } }
          </mat-error>
          }
        </mat-form-field>
      </form>
    </mat-dialog-content>
    <mat-dialog-actions>
      <button
        (click)="onSubmit()"
        [disabled]="!taskForm.valid"
        mat-raised-button
        color="primary"
        type="submit"
      >
        Create
      </button>
    </mat-dialog-actions>
    

    TS:

    import { Component } from '@angular/core';
    import { CommonModule, JsonPipe } from '@angular/common';
    import { MatButtonModule } from '@angular/material/button';
    import { MatCheckbox } from '@angular/material/checkbox';
    import {
      MatError,
      MatFormField,
      MatFormFieldModule,
      MatLabel,
    } from '@angular/material/form-field';
    import {
      AbstractControl,
      FormBuilder,
      FormControl,
      FormGroup,
      FormsModule,
      ReactiveFormsModule,
      ValidationErrors,
      ValidatorFn,
      Validators,
    } from '@angular/forms';
    import { MatDatepickerModule } from '@angular/material/datepicker';
    import { MatInputModule } from '@angular/material/input';
    import { MatOption } from '@angular/material/core';
    import { MatButtonToggleModule } from '@angular/material/button-toggle';
    import { TitleCasePipe } from '@angular/common';
    import { MatSelect } from '@angular/material/select';
    import { MatIconModule } from '@angular/material/icon';
    import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
    import { MatDialogModule } from '@angular/material/dialog';
    import { MatNativeDateModule } from '@angular/material/core';
    
    @Component({
      selector: 'app-task-form',
      standalone: true,
      imports: [
        CommonModule,
        FormsModule,
        MatButtonModule,
        MatCheckbox,
        MatError,
        MatFormField,
        MatLabel,
        ReactiveFormsModule,
        MatDatepickerModule,
        MatInputModule,
        MatFormFieldModule,
        MatButtonToggleModule,
        TitleCasePipe,
        MatSelect,
        MatOption,
        MatRadioGroup,
        MatRadioButton,
        MatDialogModule,
        MatIconModule,
        JsonPipe,
        MatDatepickerModule,
        MatNativeDateModule,
      ],
      templateUrl: './task-form.component.html',
      styleUrl: './task-form.component.css',
    })
    export class TaskFormComponent {
      taskForm!: FormGroup;
      constructor(private fb: FormBuilder) {}
    
      ngOnInit() {
        this.taskForm = this.fb.group({
          dueTo: new FormControl('', [Validators.required]),
        });
      }
    
      public onSubmit() {}
    
      private dateFormatValidator(): ValidatorFn {
        return (control: AbstractControl): ValidationErrors | null => {
          if (!control.value) {
            return null;
          }
          const validFormat = /^\d{1,2}\/\d{1,2}\/\d{4}$/;
          return validFormat.test(control.value)
            ? null
            : { invalidDateFormat: true };
        };
      }
    }
    

    Stackblitz Demo