Margerine
Margerine

Reputation: 183

Date validator in Angular 8 to compare two dates

What is the best way to do this in Angular 8

i have two inputs that are dates fromDateMin and fromDateMAx i need to compare these two against formControlName='fromctrlname' and formControlName='toctrlname' and display errors

Conditions:

If fromDateMin is non-null, the user can't select a date any earlier than this date. If they do, the date is changed to this date. Display message in error-message-span.

If fromDateMax is non-null, the user can't select a date any later than this date. If they do, the date is changed to this date. Display message in error-message-span.

fromDate must be earlier than, or the same as, toDate. toDate must be later than, or the same as, fromDate. Display message in error-message-span.

Here is what i have tried so far

https://stackblitz.com/edit/angular-2gdadn?file=src%2Fapp%2Fapp.component.ts

Here is my html


    <form [formGroup]='dateFilterForm'>
      <label *ngIf="fromLabel != ''" for="{{componentId}}-fromDate">{{fromLabel}}</label>
      <input type="date" formControlName='fromctrlname' id="{{componentId}}-fromDate" name="{{componentId}}-fromDate"
        placeholder="{{fromPlaceholder}}" [(ngModel)]="fromDate" (ngModelChange)="errMsg = ''" [value]="fromDate">
    
      <label *ngIf="toLabel != ''" for="{{componentId}}-toDate">{{toLabel}}</label>
      <input type="date" formControlName='toctrlname' id="{{componentId}}-toDate" name="{{componentId}}-fromDate"
        placeholder="{{toPlaceholder}}" [(ngModel)]="toDate" (ngModelChange)="errMsg = ''" [value]="toDate">
    
      <button type="submit" (click)="submit()"
        class="btn button-border white icon-rightarrow-white medium mt2 ml2 mr1 mb2 r15 fw7"
        style="display:inline-block;">{{buttonLabel}}</button>
    
      <div class="error-message mb3 red flex items-center fw7"
        *ngIf="formInvalid && dateFilterForm.controls.fromctrlname.hasError('invalidfromDate')">
        <i class="icon-alert s15 mr2"></i> From date should not be earlier than {{fromDateMin}}.
      </div>
    
      <div class="error-message mb3 red flex items-center fw7"
        *ngIf="formInvalid && dateFilterForm.controls.fromctrlname.hasError('invalidtoDate')">
        <i class="icon-alert s15 mr2"></i>To date should not be later than {{fromDateMax}}.
      </div>
      
      <span class="error-message mb3 red flex items-center fw7" *ngIf="errMsg!=''">
        <i class="icon-alert s15 mr2"></i>{{errMsg}}
      </span>
    </form>

Upvotes: 2

Views: 18191

Answers (1)

tehshin
tehshin

Reputation: 866

The validation part can be easily achieved using reactive forms and custom validators, as mentioned by Robert.

You can create 2 validators based on your requirements, one that validates min and max date on each form control within the group. Another validator for the group to validate that fromDate is earlier than toDate.

This is how the validator for the input controls basically could look like.

function dateRangeValidator(min: Date, max: Date): ValidatorFn {
  return control => {
    if (!control.value) return null;

    const dateValue = new Date(control.value);

    if (min && dateValue < min) {
      return { message: 'error message' };
    }

    if (max && dateValue > max) {
      return { message: 'error message' };
    }

    null;
  }
}

You can compose a ValidatorFn by wrapping it around another function to provide additional parameters besides the control. In your case that would be a min and max date. You then add the validator to the form by calling your function.

fromDate: new FormControl('', [dateRangeValidator(this.minDate, this.maxDate)])

In your group validator you compare the values of both child controls

function groupValidator(group: AbstractControl): ValidationErrors | null {
  const fromCtrl = group.get('fromDate');
  const toCtrl = group.get('toDate');

  return new Date(fromCtrl.value) > new Date(toCtrl.value) ? { message: 'error message' } : null;
}

In this case we don't wrap the validation function around another function, because we don't have to configure anything.

The complete form group configuration will look something like this:

formGroup = new FormGroup({
    fromDate: new FormControl('', [dateRangeValidator(this.minDate, this.maxDate)]),
    toDate: new FormControl('', [dateRangeValidator(this.minDate, this.maxDate)]),
}, [groupValidator]);

Each validator will add the errors to the corresponding control. So they are easy to get to in the template or somewhere else.

These are very rough example implementations, so use them only as a starting point.

As for the updating the value during validation. You could do it in the validator, as you do have the AbstractControl available. Calling setValue or patchValue can do that for you. But that will trigger validation again and can lead to an infinite recursive loop, so watch out for that. Also, you might want to change that a value update is emitted on blur, and not on each keystroke. You can do that on each form control using the updateOn: blur option.

fromDate: new FormControl('', { updateOn: 'blur', validators: [...] })

Here is the stackblitz I used for a quick prototype: https://stackblitz.com/edit/angular-ivy-jfes5n

Upvotes: 6

Related Questions