I'm using reactive forms and a custom validator in Angular 11. I want to flag an error at the form level when the user selects an EndDate
that is earlier than the StartDate
. I'll use this form-level error to display error text on the template as well as to disable the button to submit the form.
There seem to be two issues here that I can't figure out:
1. When I set an error via the customValidator on the form, the form.status
remains VALID
even when there are form.errors
. Why is this?
2. In the template, checking the form's errors doesn't work. I have a mat-error
with form.hasErrors('endDateTooSoon')
, and yet this error never displays. What am I missing here? Something odd.
This is the form instantiation.
shiftEditorForm = new FormGroup({
shiftTitle: new FormControl('', Validators.required),
shiftStartDate: new FormControl('', Validators.required),
shiftStartTime: new FormControl('', Validators.required),
shiftEndDate: new FormControl('', Validators.required),
shiftEndTime: new FormControl('', Validators.required),
}, DateValidators.compareDates('shiftStartDate', 'shiftEndDate', { endDateTooSoon: true})
);
This is the validator:
static compareDates = (
dateField1: string,
dateField2: string,
validatorField: { [key: string]: boolean }): ValidatorFn => {
return (control: AbstractControl): { [key: string]: boolean } | null => {
const date1 = control.get(dateField1)?.value;
const date2 = control.get(dateField2)?.value;
if (date1 && date2 && date1 > date2) {
return validatorField;
}
return null;
};
}
This is what the form looks like when the validator works:
(Why is it VALID when an error has been correctly added? )
This is the template block defining the shiftStartDate
formControl:
<mat-form-field class="date-selection"
appearance="outline">
<input matInput
[matDatepicker]="startDate"
formControlName="shiftStartDate">
<mat-datepicker-toggle matSuffix [for]="startDate"></mat-datepicker-toggle>
<mat-datepicker #startDate></mat-datepicker>
<mat-error *ngIf="shiftStartDate?.touched && !shiftStartDate?.value">
Shift start date is required.
</mat-error>
<mat-error *ngIf="shiftEditorForm?.hasError('endDateTooSoon')">
End date must be after start date.
</mat-error>
</mat-form-field>
I'm checking with *ngIf="shiftEditorForm.hasError()
and yet it never displays the error.
There's another similar block of the template for the shiftEndDate
formControl.
And yet here as I manipulate the datepickers, and set the shiftEndDate
to be before the shiftStartDate
, nothing happens and the error doesn't display:
Update: An added wrinkle I found after posting this question. The button to submit the form IS disabled/enabled based on validity of the date check, with the following attribute on the button
: [disabled]="this.shiftEditorForm.invalid"
. Real head-scratcher here: the formGroup shows status as VALID
but is returning true for this invalid
boolean check.
I think I have been able to reproduce your issue in this stackblitz Demo
I have noticed two things,
The Second error is what we will try to look at
To understand the problem I will state that your code works totally a expected...
Below should explain it all
Add below code under the <mat-error></mat-error>
tag
<span *ngIf="shiftEditorForm?.hasError('endDateTooSoon')">
End date must be after start date.
</span>
You will notice that this is being shown without any issues when the date error is thrown
Simply, for mat-error
to show, then the input
associated with that <mat-error>
must have an error. From the form we notice that the shiftStartDate
becomes valid once a user enters a value so <mat-error>
will not be shown, totally as expected
If you need to show the mat-error
then you will need to set the control to invalid e.g with
control.get(dateField1)?.setErrors({higherThanStart: true})
Your validatorFn
will be something like
class DateValidators {
static compareDates = (
dateField1: string,
dateField2: string,
validatorField: { [key: string]: boolean }): ValidatorFn => {
return (control: AbstractControl): { [key: string]: boolean } | null => {
const date1 = control.get(dateField1)?.value;
const date2 = control.get(dateField2)?.value;
if (date1 && date2 && date1 > date2) {
control.get(dateField1)?.setErrors({higherThanStart: true})
return validatorField;
}
control.get(dateField1)?.setErrors({higherThanStart: null})
return null;
};
}
}
Now the Error will show... See Demo Here