Angular - Dynamically add/remove validators
I have a FormGroup
defined like below:
this.businessFormGroup: this.fb.group({
'businessType': ['', Validators.required],
'description': ['', Validators.compose([Validators.required, Validators.maxLength(200)])],
'income': ['']
})
Now when businessType
is Other
, I want to remove Validators.required
validator from description
. And if businessType
is not Other
, I want to add back the Validators.required
.
I am using the below code to dynamically add/remove the Validators.required
. However, it clears the existing Validators.maxLength
validator.
if(this.businessFormGroup.get('businessType').value !== 'Other'){
this.businessFormGroup.get('description').validator = <any>Validators.compose([Validators.required]);
} else {
this.businessFormGroup.get('description').clearValidators();
}
this.businessFormGroup.get('description').updateValueAndValidity();
My question is, how can I retain the existing validators when adding/removing the required
validator.
Solution 1:
If you are using Angular 12.2 or higher, you can use the AbstractControl
methods addValidators
, removeValidators
, and hasValidator
, as per the docs:
if(this.businessFormGroup.get('businessType').value !== 'Other'){
this.businessFormGroup.get('description').addValidators(Validators.required);
} else {
this.businessFormGroup.get('description').clearValidators();
}
For older versions, Angular forms have a built in function setValidators() that enables programmatic assignment of Validators. However, this will overwrite your validators.
For your example you can do:
if(this.businessFormGroup.get('businessType').value !== 'Other'){
this.businessFormGroup.controls['description'].setValidators([Validators.required, Validators.maxLength(200)]);
} else {
this.businessFormGroup.controls['description'].setValidators([Validators.maxLength(200)]);
}
this.businessFormGroup.controls['description'].updateValueAndValidity();
It is important to keep in mind that by using this method you will overwrite your existing validators so you will need to include all the validators you need/want for the control that you are resetting.
Solution 2:
This one work for me
onAddValidationClick(){
this.formGroup.controls["firstName"].setValidators(Validators.required);
this.formGroup.controls["firstName"].updateValueAndValidity();
}
onRemoveValidationClick(){
this.formGroup.controls["firstName"].clearValidators();
this.formGroup.controls["firstName"].updateValueAndValidity();
}
Solution 3:
If you change the "validator required" more than one time (for example, using a checkbox) you should add this:
this.formGroup.controls["firstName"].setErrors(null);
So:
onAddValidationClick(){
this.formGroup.controls["firstName"].setValidators(Validators.required);
this.formGroup.controls["firstName"].updateValueAndValidity();
}
onRemoveValidationClick(){
this.formGroup.controls["firstName"].setErrors(null);
this.formGroup.controls["firstName"].clearValidators();
this.formGroup.controls["firstName"].updateValueAndValidity();
}
Solution 4:
The naive approach would be to set the validators of the control whenever the conditional variable changes. But we can actually do better than that by using some indirection + functional programming.
Consider the existence of a descriptionIsRequired
getter, that acts as a boolan flag.
Ideas:
- Create a custom validator function that takes the
descriptionIsRequired
as argument and depending on it validates a control against required + maxLength or maxLength. - Bind the custom validator to the description control in such a way, that when the validity of the control is evaluated, the newest value of
descriptionIsRequired
should be considered.
The first point is pretty straight forward to implement:
function descriptionValidator(required: boolean): ValidatorFn {
return (formControl: FormControl): ValidationErrors => {
if (required) {
return Validators.compose([Validators.required, Validators.maxLength(200)])(formControl);
} else {
return Validators.maxLength(200)(formControl);
}
}
}
Notice that this is a self capsulated function.
The second point is a little bit more tricky, but in the end it looks like this:
export class FooComponent {
constructor(){
this.form = fb.group({
description: ['initial name', this.validator()]
});
}
private get descriptionIsRequired(): boolean {
...
}
private validator(): ValidatorFn {
return (c: FormControl): ValidationErrors => descriptionValidator(this.descriptionIsRequired)(c);
}
}
A small explanation of what is happening:
- the
validator
method returns a function - the function returned by
validator
could be considered a factory method: whenever its invoked, returns a new function, more specifically, a new instance of ourdescriptionValidator
using the newestdescriptionIsRequired
value.
A live demo in the following stackblitz
Solution 5:
Maybe this helps:
Adding Validators.required to the validatorset of an existing AbstractControl
:
if (c.validator !== null) {
c.setValidators([c.validator, Validators.required])
} else {
c.setValidators([Validators.required])
}