1

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>
5
  • Why are you using this weird @switch(true) statement? Wouldn't it be much easier to use @if and @else if instead? Commented Jul 15, 2024 at 14:34
  • 1
    @JSONDerulo less code when more conditionals Commented Jul 15, 2024 at 14:34
  • It's not less code. You need the switch, and there will be one more level of indentation. Assuming indentation is 2 spaces, it's 1 characters less per condition but 15 additional characters for the switch statement. Is's only really less if you have 16 conditions or more. Commented Jul 15, 2024 at 14:35
  • 1
    @JSONDerulo opinion based, can we leave it at that Commented Jul 15, 2024 at 14:36
  • Back to the question. Your validator is just not working. It expects a string, but in reality you have a date object, which is implicitly converted to a string, but the string conversion does not follow the expected format at all. Also, the Material Datepicker already performs date parsing. And if that parsing fails, the datepicker will reset the value. So your validator always produces wrong results. Commented Jul 15, 2024 at 14:55

1 Answer 1

1

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.