0

As per the question, I have a date range picker with start and end dates as part of a mat-form-field. I want to perform a custom validation (for example, to ensure the absolute difference between start and end is not longer than 15 days) and display a mat-error inside the mat-form-field informing the user about the issue.

I also want to have multiple such validators and error messages. The errors are set on the form group correctly, but they are not displayed since the form field does not identify those specific errors as part of the field's "invalidating" set of errors. I got it to work by doing a nasty workaround setting the matStartDateInvalid or matEndDateInvalid errors on the start or end input fields, but this just is not something I'm okay with.

Here's a stackblitz highlighting the issue: Stackblitz

How can I do this the right way?

succerburg
  • 3
  • 1
  • 3

2 Answers2

2

I get it!

The key is use a custom ErrorStateMatcher. If you defined some like

export class RangeStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control && form && form.invalid && (control.dirty || control.touched || isSubmitted));
  }
}

We can asign the errorStateMatcher to this function. See that this RangeStateMAtcher implied that the control is valid or not if the form is valid or not

The problem is that I can not put in .html

 <!--this give ERRROR-->
<input matEndDate [errorStateMatcher]="matcher" formControlName="end" 
       [placeholder]="'end'">

So, we need use a ViewChild (I like use a template reference variable)

  @ViewChild('end',{read: MatEndDate,static:true}) endControl: MatEndDate<any>
  //and in ngOnInit

  ngOnInit() {
    ....
    this.endControl.errorStateMatcher=new RangeStateMatcher()
  }

  //in .html 
  <input #end  matEndDate formControlName="end" [placeholder]="'end'">

See the stackbliz

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • Yep, this is what I was looking for. Although I'd add the same type of error state matcher to the start date input as well, for consistency's sake, and remove the on blur validation from the start date input. Thanks! – succerburg Jul 17 '20 at 07:41
  • ::glups:: I left the (blur) in the stackblitz, and as you comment it's unnecessary. Any way I feel that if you **only** add the matcher to the end, work too – Eliseo Jul 17 '20 at 07:52
1

You can create the range error attached to the control 'end'. make also a getter of the control

this.dateRange = new FormGroup({
  start: new FormControl(),
  end: new FormControl(null, this.validateDateRange)
});

//a getter, see the explain below
get endControl() {
   return this.searchForm?(this.searchForm.get('dateRange') as FormGroup).get('end'):null
}


  validateDateRange(control: FormControl): ValidationErrors {
    const controlGroup=control.parent //<--here we get the formGroup "parent"
    if (controlGroup) {
      const toDate = controlGroup.value.end;
      const fromDate = controlGroup.value.start;
      if (Math.round((toDate - fromDate) / (1000 * 60 * 60 * 24)) > 15) {
        return {
          rangeExceeded: true
        };
      }
    }
    return null;
  }

if we write the .html as

  <mat-form-field>
    <mat-label>Search range</mat-label>
    <mat-date-range-input [formGroup]="dateRange" [rangePicker]="rangePicker">
      <input matStartDate  formControlName="start" [placeholder]="'start'" (blur)="endControl.updateValueAndValidity()">
      <input matEndDate formControlName="end" [placeholder]="'end'">
    </mat-date-range-input>
    <mat-error >
      Internal mat form field error on dateRange FormGroup: range exceeded
    </mat-error>
    <mat-datepicker-toggle matSuffix [for]="rangePicker"></mat-datepicker-toggle>
    <mat-date-range-picker #rangePicker></mat-date-range-picker>

  </mat-form-field>

we get the result.

IMPORTANT: See the "strange" (blur)="endControl.updateValueAndValidity()" in the input "start". This is because else, if we change manually the "start", Angular don't check the validation of the "end"

See the stackblitz

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • This looks like a valid solution, but I still consider it a workaround; the big issues I see with it are: 1. that the more validations we'd need to apply on the fields, the code complexity grows more than linearly and consequently, the component becomes harder to maintain; and 2. this does not allow having a compound validator on the dateRange form group AND validators on the formgroup's fields, start and end dates without yet another workaround. – succerburg Jul 17 '20 at 06:47
  • I get it!, see my new answer – Eliseo Jul 17 '20 at 07:23