35
loading...
This website collects cookies to deliver better user experience
AbstractControl
parameter. In single-control validators, the control is normally a FormControl
. However, for multi-control validators, I need to pass in the parent FormGroup
as the control. Doing this gives me the scope of all of the children controls inside of the FormGroup
. To make this validator more reusable, I also pass in the names of the controls I want to compare. I also can pass in the name of the kind of values I am comparing to make the error messages more dynamic.FormGroup
as the AbstractControl
instead of a specific FormControl
, if I want to set errors on the FormControls
, I need to call setErrors()
on the specific control. Otherwise, if I just return the ValidationErrors
, they will apply to the FormGroup
, which isn’t what I want here.export class MatchFieldValidator {
static validFieldMatch(
controlName: string,
confirmControlName: string,
fieldName: string = 'Password',
): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const controlValue: unknown | null = control.get(controlName)?.value;
const confirmControlValue: unknown | null = control.get(
confirmControlName,
)?.value;
if (!confirmControlValue) {
control.get(confirmControlName)?.setErrors({
confirmFieldRequired: `Confirm ${fieldName} is required.`,
});
}
if (controlValue !== confirmControlValue) {
control
.get(confirmControlName)
?.setErrors({ fieldsMismatched: `${fieldName} fields do not match.` });
}
if (controlValue && controlValue === confirmControlValue) {
control.get(confirmControlName)?.setErrors(null);
}
return null;
};
}
}
FormControls
, I need to attach the validator to the parent FormGroup
. The FormBuilder
takes an options argument after the control config where I can pass in validators. I add the match field validator, along with the names of the controls I want to compare, and what kind of field I’m comparing. I’ve simplified the below code to just focus on what is relevant:private createForm(): FormGroup {
const form = this.fb.group({
password: [
'',
Validators.compose([PasswordValidator.validPassword(true)]),
],
confirmPassword: [''],
},
{
validators: Validators.compose([
MatchFieldValidator.validFieldMatch('password', 'confirmPassword', 'Password'),
]),
});
return form;
}
KeyValuePipe
for simplicity.<div class="field-group">
<mat-form-field>
<input
name="password"
id="password"
type="password"
matInput
placeholder="Password"
formControlName="password"
/>
<mat-error *ngIf="form.get('password')?.errors">
<ng-container *ngFor="let error of form.get('password')?.errors | keyvalue">
<div *ngIf="error.key !== 'required'">{{ error.value }}</div>
</ng-container>
</mat-error>
</mat-form-field>
<mat-form-field>
<input
name="confirmPassword"
id="confirmPassword"
type="password"
matInput
placeholder="Confirm Password"
formControlName="confirmPassword"
required
/>
<mat-error *ngIf="form.get('confirmPassword')?.errors">
<ng-container *ngFor="let error of form.get('confirmPassword')?.errors | keyvalue">
<div *ngIf="error.key !== 'required'">{{ error.value }}</div>
</ng-container>
</mat-error>
</mat-form-field>
</div>
describe('validFieldMatch() default field name', () => {
const matchFieldValidator = MatchFieldValidator.validFieldMatch(
'controlName',
'confirmControlName',
);
const form = new FormGroup({
controlName: new FormControl(''),
confirmControlName: new FormControl(''),
});
const controlName = form.get('controlName');
const confirmControlName = form.get('confirmControlName');
it(`should set control error as { confirmFieldRequired: 'Confirm Password is required.' } when value is an empty string`, () => {
controlName?.setValue('');
confirmControlName?.setValue('');
matchFieldValidator(form);
const expectedValue = {
confirmFieldRequired: 'Confirm Password is required.',
};
expect(confirmControlName?.errors).toEqual(expectedValue);
});
it(`should set control error as { fieldsMismatched: 'Password fields do not match.' } when values do not match`, () => {
controlName?.setValue('password123!');
confirmControlName?.setValue('password123');
matchFieldValidator(form);
const expectedValue = {
fieldsMismatched: 'Password fields do not match.',
};
expect(confirmControlName?.errors).toEqual(expectedValue);
});
it(`should set control error as null when values match`, () => {
controlName?.setValue('password123!');
confirmControlName?.setValue('password123!');
matchFieldValidator(form);
expect(controlName?.errors).toEqual(null);
expect(confirmControlName?.errors).toEqual(null);
});
it(`should set control error as null when control matches confirm after not matching`, () => {
controlName?.setValue('password123!');
confirmControlName?.setValue('password123!');
matchFieldValidator(form);
controlName?.setValue('password123');
matchFieldValidator(form);
controlName?.setValue('password123!');
matchFieldValidator(form);
expect(controlName?.errors).toEqual(null);
expect(confirmControlName?.errors).toEqual(null);
});
it(`should set control error as null when confirm matches control after not matching`, () => {
controlName?.setValue('password123!');
confirmControlName?.setValue('password123!');
matchFieldValidator(form);
controlName?.setValue('password123');
matchFieldValidator(form);
confirmControlName?.setValue('password123');
matchFieldValidator(form);
expect(controlName?.errors).toEqual(null);
expect(confirmControlName?.errors).toEqual(null);
});
});