Angular - Dynamically add/remove validators
AngularAngular FormsAngular ValidationAngular Problem Overview
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.
Angular Solutions
Solution 1 - Angular
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 - Angular
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 - Angular
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 - Angular
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 - Angular
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])
}
Solution 6 - Angular
Anyone still looking for an answer, you can do it like this ,handle it in ngOnInit() or any place that you like.
const validators = formGroup.validator; /* or control.validator */
const newValidator = CustomValidator.checkUserNameValidity();
/* Add to existing validator */
if(validator) {
formGroup.setValidators([validators, newValidator])
} else {. /* if no validators added already */
formGroup.setValidators([newValidator]);
}
Follow the samething for asyncValidator as well.
Solution 7 - Angular
I have Forms that depending on the context require some values but they can be omitted in others. In my case it wasn't enough to call the 'mainform.updateValueAndValidity()'.I had to explicitly update that field when I was dynamically setting the require validation:
formField.setValidators([RxwebValidators.required()]);
formField.updateValueAndValidity({onlySelf: true});
The key was {onlySelf: true}
Solution 8 - Angular
If you change the "validator required" more than one time (for example, using a checkbox) you should add this: Very important field! this.formGroup.controls["firstName"].setErrors(null);