I have a custom validator on the field postalCode
:
function postalCodeValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!this.contactForm) {
return null;
}
const isPotalCodeRequired = this.contactForm.get(
'isPotalCodeRequired'
).value;
if (isPotalCodeRequired && !control.value) {
console.log('here');
this.contactForm.controls['postalCode'].setErrors({ required: true });
} else {
this.contactForm.controls['postalCode'].setErrors({ required: false });
}
return null;
};
}
which checks another field isPotalCodeRequired
to see if validation should be applied to the postalCode
field or not.
If isPotalCodeRequired
is true, a value is required for postalCode
else it can be left empty. But my custom validation doesn't seem to work as expected when I call setErrors
on the postalCode
field. It adds it, within the custom validator function, but checking it after the function executes, the error is no longer present on the postalCode
field.
Demo.
CodePudding user response:
Angular's validator functions are a little bit weird. You need to return null
when the control has no errors and an object containing the errors and a brief description when they are incorrect:
function postalCodeValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!this.contactForm) {
return null;
}
const isPotalCodeRequired = this.contactForm.get(
'isPotalCodeRequired'
).value;
if (isPotalCodeRequired && !control.value) {
console.log('here');
return {required: 'Postal code is required' };
}
return null;
};
}
You don't need to manually set the errors as the framework will do it for you
CodePudding user response:
What you are trying to do is called cross-field validation and typically with a cross-field validator you should be applying the validator the FormGroup
and not to the FormControl
. Here,
this.contactForm = this.formBuilder.group({
isPotalCodeRequired: [true],
postalCode: [null, Validators.compose([postalCodeValidator.call(this)])],
});
you are binding your postalCodeValidator to a specific control. When you apply a validator like this, that validator is expected to return the validation messages that you want applied to the control but you are returning null,
if (isPotalCodeRequired && !control.value) {
console.log('here');
this.contactForm.controls['postalCode'].setErrors({ required: true });
} else {
this.contactForm.controls['postalCode'].setErrors({ required: false });
}
return null;
Which clears all he validation messages that are applied to that control. Instead, bind this validator to the FormGroup
containing both of your controls,
this.contactForm = this.formBuilder.group({
isPotalCodeRequired: [true],
postalCode: [null]
}, {validators: Validators.compose([postalCodeValidator.call(this)])});
Now the AbstractControl that gets passed into the validator is your entire FormGroup. This has the benefit of providing access to all of the controls that you need. So instead of references to this.contactForm
you would have,
control.controls['postalCode'].setErrors({ required: true });
You should be able to remove all references to this.contactForm
from inside the validator. More importantly, your null return is no longer going to clear the validation messages on the individual control. Check out the Angular docs on cross-field validation for reactive forms.
But there is a cleaner way to do this altogether. Instead of writing a custom validator, you can just listen for changes on isPostalCodeRequired and add/remove the built-in required validator as needed,
this.contactForm.get('isPostalCodeRequired').valueChanges.subscribe(val => {
if (val) {
//Add validator
else {
//Remove validator
})
The helper functions available to add/remove validators depend on your Angular version but they are pretty straightforward.
EDIT: Updated the answer to address the code in the demo.