95

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.

A J Qarshi
  • 2,772
  • 6
  • 37
  • 53
  • the validators are storage like a array , you will need to handle the list of validators applied by yourself – Ricardo Mar 02 '18 at 18:51
  • sadly this is not possible, angular seems to merge the applied validators internally therefore you can only call `clear` and `set` functions – Nickolaus Mar 02 '18 at 19:28
  • @Ricardo thats wrong, validators are composed into a single function and thats it. With the current implementation of the API its not possible to check which validators are set for a control – Jota.Toledo Mar 02 '18 at 20:24
  • 1
    As of 12.2 (August 2021), you can now use the new methods `addValidators`, `removeValidators`, `hasValidator`, and their async counterparts. https://angular.io/api/forms/AbstractControl#addvalidators – dylhunn Aug 10 '21 at 01:38

8 Answers8

150

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.

dylhunn
  • 989
  • 2
  • 8
  • 25
Narm
  • 10,677
  • 5
  • 41
  • 54
  • 5
    note that the setValidators will overwrite previously setted validators, meaning that the maxLength will be overwritten and wont be regenerated with this approach – Jota.Toledo Mar 02 '18 at 19:36
  • Thanks @Jota.Toledo. That is an important behavior to be aware of. Updated the answer to include it. – Narm Mar 02 '18 at 19:50
  • 1
    I think that use a customValidator that take account of bussinesType and description is a "more natural" idea that remove/add validators – Eliseo Mar 02 '18 at 21:08
  • 3
    @Eliseo you are right that having a custom validator is better approach but in my case I have validation rules defined in a json file and I am looking for a more generic implementation and solution provided by Narm is probably the way to go. – A J Qarshi Mar 03 '18 at 11:48
  • 2
    For me this only worked after calling updateValueAndValidity() afterwards, as suggested in the linked docs (using Angular version 8) – LeBavarois Mar 30 '20 at 09:48
51

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();
}
Kalyan
  • 1,395
  • 2
  • 13
  • 26
San Jaisy
  • 15,327
  • 34
  • 171
  • 290
26

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();
      }
Kike Lebowski
  • 501
  • 6
  • 6
8

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

Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
  • +1 It's valid solution but I am looking for a more generic implementation as my validation rules are defined in a json file. – A J Qarshi Mar 03 '18 at 15:05
  • I wonder what do you mean by generic in this context. The only way I see for this to be generalized is that you have a collection of validators `V` that always have to be active in a control. Where they come from is irrelevant. Then you have another collection of validators `Vop`, in this case a collection with only required in it, that can and can not be active in a control depending on a boolean condition. Am I right? If no, please further explain what is your concept of "more generic" implementation. – Jota.Toledo Mar 03 '18 at 15:14
  • cheers! this gave me a good idea to do something generic I needed to do – Juan Stoppa Sep 13 '18 at 22:05
4

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])
  }
nircraft
  • 8,242
  • 5
  • 30
  • 46
Rob
  • 41
  • 1
1

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.

sudharsan tk
  • 494
  • 6
  • 14
1

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

user11764588
  • 11
  • 1
  • 1
1

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}

fuegonju
  • 111
  • 1
  • 7