Reputation: 1060
@angular/forms: 6.1.7
I'm trying to create a custom validator that checks 2 formControl's for inconsistent.
When following the official angular documentation I get a error when inputting value to one of the 2 forms:
Uncaught Error: Expected validator to return Promise or Observable.
at toObservable (forms.js:596)
at Array.map (<anonymous>)
at FormControl.asyncValidator (forms.js:584)
at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.AbstractControl._runAsyncValidator (forms.js:2454)
at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.AbstractControl.updateValueAndValidity (forms.js:2427)
at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.FormControl.setValue (forms.js:2764)
at updateControl (forms.js:1699)
at DefaultValueAccessor.onChange (forms.js:1684)
at DefaultValueAccessor.push../node_modules/@angular/forms/fesm5/forms.js.DefaultValueAccessor._handleInput (forms.js:741)
at Object.eval [as handleEvent] (ChangePasswordComponent.html:13)
It looks like angular is trying to fins a asyncValidator in this case, and not the sync version that i expect.
Worth to mention is that i also have tried to return a Observable<ValidationErrors | null>
that gives me the same error output.
Validator:
import { FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
export const passwordMatchValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const password = control.get('password');
const confirmPassword = control.get('confirmPassword');
if (!password || !confirmPassword) {
return null;
}
return password === confirmPassword ? null : { passwordMismatch: true };
};
Implementation:
this.formGroup = this.formBuilder.group(
{
password: ['', Validators.required, Validators.minLength(6)],
confirmPassword: ['', Validators.required, Validators.minLength(6)]
},
{
validators: passwordMatchValidator
}
How do i create a custom synchronous cross field validator?
Is it possible to pass in the formControl names to the function instead of hard coding them?
import { FormGroup, ValidationErrors, ValidatorFn } from "@angular/forms";
export const matchValidator = (firstControlName: string, secondControlName: string): ValidatorFn => {
return (control: FormGroup): ValidationErrors | null => {
const firstControlValue = control.get(firstControlName).value;
const secondControlValue = control.get(secondControlName).value;
if (!firstControlValue || !secondControlValue) {
return null;
}
return firstControlValue === secondControlValue ? null : { mismatch: true };
}
};
Implementation:
this.formGroup = this.formBuilder.group(
{
password: ['', [Validators.required, Validators.minLength(6)]],
confirmPassword: ['', [Validators.required, Validators.minLength(6)]],
currentPassword: ['', Validators.required]
},
{
validator: matchValidator('password', 'confirmPassword')
}
Upvotes: 2
Views: 1712
Reputation: 7264
Based on the documentation, you should pass validator
property into extra
param for group
function in FormBuilder
. See the docs. Second problem is that you should pass an array as second argument when creating form control and you set the validators directly with formBuilder
:
password: ['', [Validators.required, Validators.minLength(6)]]
because currently the minLength
validator is treated as asyncValidator
as it is third argument.
Side question
You could create a validator factory function which creates the validator for you and takes 2 controls:
export const passwordMatchValidator = (passwordControl: AbstractControl, confirmPasswordControl: AbstractControl): ValidatorFn => {
return (control: FormGroup): ValidationErrors | null => {
const password = passwordControl.value;
const confirmPassword = confirmPasswordControl.value;
if (!password || !confirmPassword) {
return null;
}
return password === confirmPassword ? null : { passwordMismatch: true };
}
};
and usage:
passwordMatchValidator(this.formGroup.get('password'),this.formGroup.get('confirmPassword'))
;
or the factory function could just take string params instead of controls and extract the form controls itself.
Upvotes: 3