Search code examples
angularangular-materialangular-reactive-forms

Angular Material time input validation in a ReactiveForm


I'm working in Angular 17 with Material. Lets say I have an example of Reactive Form with 2 inputs of time type inside it.

[minHour, maxHour] should be an intervall range of Hours, let's say [09:00 , 13:00].

HTML [my-time.component.html]

<form [formGroup]="timeFormGroup" (ngSubmit)="save()">
   
 <div style="display:flex">  
  <mat-form-field style="width: 100px;" appearance="outline">                         
   <mat-label>Min Hour</mat-label>
     <input type="time" matInput formControlName="minHour">
  </mat-form-field>                           

   <mat-form-field style="width: 100px;" appearance="outline">
      <mat-label>Max Hour</mat-label>
        <input type="time" matInput formControlName="maxHour">
   </mat-form-field>
 </div>
  ...
    <div>
       ...
       <button mat-flat-button color="accent" type="submit">Save</button>
    </div>

</form>

[TS] my-time.component.ts

import { Component, Inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input'
import { FormBuilder, Validators } from '@angular/forms';

....

@Component({
  selector: 'app-my-time',
  templateUrl: './my-time.component.html',
  styleUrl: './my-time.component.scss'
})
export class MyTimeComponent implements OnInit {

constructor(private fb: FormBuilder){}

  timeFormGroup = this.fb.group({
    minTime: ['', Validators.required],
    maxTime: ['', Validators.required]

  });



  ngOnInit(): void {

  }

  save() {
    console.log(this.timeFormGroup.value)
  }

}

I need to check that this range is respected when validating the input. I also need to display an error message when the input is different from the range 09:00 - 13:00.

Thanks to anyone who will help me, expecially with some code example


Solution

  • I may have needed more information, but I'll try out with what I have.

    Custom validator

    I would construct a specific validator for this purpose.

      timeFormGroup = this.fb.group({
        minTime: ['', [Validators.required, this._minTimeValidator('09:00')]],
        maxTime: ['', Validators.required]
      });
    
      private _minTimeValidator(minTime: string): ValidatorFn {
        return (control: AbstractControl): ValidationErrors | null => {
          const inputTime = control.value as string
    
          if (!inputTime) {
            return null // If the control is empty, don't perform validation
          }
    
          const minTimeObj = new Date(`1970-01-01T${minTime}`)
          const inputTimeObj = new Date(`1970-01-01T${inputTime}`)
    
          return inputTimeObj >= minTimeObj ? null : { minTime: true }
        }
      }
    

    That way, minTime will be wrong if the user select less than 09:00.
    you can do the same with the maxTime.

    Set validator on the fly

    If you need to set the min (or the max) validators base on the min (or the max) time. you could subscribe to the formControl change

    With an unsubscribe "proTip" logic

    protected _unsubscribe$: Subject<void> = new Subject()
    
    ngOnInit(): void {
      this.timeFormGroup.valueChanges
        .pipe(
          filter((event) => (event ? true : false)),
          takeUntil(this._unsubscribe$),
        )
        .subscribe((value) => {
          const minTime = value.minTime.split(':') // Could be useful
          const maxTime = value.maxTime.split(':') // Could be useful
          const minTimeControl = this.timeFormGroup.get('minTime')
    
          if (
            // Your logic : If true
          ) {
            const customTime = '09:00' // Whatever you like, use minTime or maxTime if you wish
            minTimeControl.setValidators(this._minTimeValidator(customTime))
          } else if (minTimeControl.validator) { // If we have validator
            minTimeControl.setValidators(null)
            minTimeControl.updateValueAndValidity()
          }
        })
    }
    
    ngOnDestroy(): void {
      this._unsubscribe$.next()
      this._unsubscribe$.complete()
    }