Angular - Dynamically add/remove validators

AngularAngular FormsAngular Validation

Angular 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 our descriptionValidator using the newest descriptionIsRequired 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);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionA J QarshiView Question on Stackoverflow
Solution 1 - AngularNarmView Answer on Stackoverflow
Solution 2 - AngularSan JaisyView Answer on Stackoverflow
Solution 3 - AngularKike LebowskiView Answer on Stackoverflow
Solution 4 - AngularJota.ToledoView Answer on Stackoverflow
Solution 5 - AngularRobView Answer on Stackoverflow
Solution 6 - Angularsudharsan tkView Answer on Stackoverflow
Solution 7 - AngularfuegonjuView Answer on Stackoverflow
Solution 8 - Angularuser11764588View Answer on Stackoverflow