Angular - Dynamically add/remove validators

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();
}

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


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();
      }

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.