8

I am trying to add and remove validators in a formGroup controls based on certain condition.

When I am updating the validators through formGroup.updateValueAndValidity() for whole form its not updating, where as if I am specifically applying for each Control i.e. formGroup.get('formControl').updateValueAndValidity(), it is working but i have to write for each control which is i hope not the correct way. What am i doing wrong?

if (data == 'x') {
    this.myForm.get('control2').setValue(null);
    this.myForm.get('control2').setValidators(Validators.nullValidator);
    this.myForm.get('control1').setValidators(Validators.required);
} else if (data == 'y') {
    this.myForm.get('control1').setValue(null);
    this.myForm.get('control1').setValidators(Validators.nullValidator);
    this.myForm.get('control2').setValidators(Validators.required);
}
this.myForm.get('control1').updateValueAndValidity();
this.myForm.get('control2').updateValueAndValidity();

this is working, but,

this.myForm.updateValueAndValidity();

this is not working.

Dino
  • 6,207
  • 8
  • 28
  • 62
Ashutosh Singh
  • 167
  • 1
  • 1
  • 6
  • 1
    I believe that unfortunately you will have to target each controller individually. Additionally you could write one method that will do that for you every time it is called if you need to reuse it multiple times. – mikegross Nov 13 '19 at 08:47

5 Answers5

15

updateValueAndValidity() is bottom-up, so if you call this method over a control, it will check only validations of this control and their parents, but not their children.

For more details, see AbstractControl#updateValueAndValidity on github to how it works.

  updateValueAndValidity(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
    this._setInitialStatus();
    this._updateValue();

    if (this.enabled) {
      this._cancelExistingSubscription();
      (this as{errors: ValidationErrors | null}).errors = this._runValidator();
      (this as{status: string}).status = this._calculateStatus();

      if (this.status === VALID || this.status === PENDING) {
        this._runAsyncValidator(opts.emitEvent);
      }
    }

    if (opts.emitEvent !== false) {
      (this.valueChanges as EventEmitter<any>).emit(this.value);
      (this.statusChanges as EventEmitter<string>).emit(this.status);
    }

    if (this._parent && !opts.onlySelf) {
      this._parent.updateValueAndValidity(opts);
    }
  }
Serginho
  • 6,213
  • 2
  • 21
  • 45
  • 9
    What on earth is the point of that? So if I would like to trigger a check on a whole FormGroup, I'm supposed to call it only on one control?? What were they thinking??? – AsGoodAsItGets Apr 08 '20 at 08:14
5

I stumbled upon this last week too, and I came to the conclusion that this is the expected behavior. The docs states the following:

By default, it also updates the value and validity of its ancestors.

Note that it says "ancestor" and not "descendants". This means that when you have run updateValueAndValidity() on control1 and control2, and they're both valid, myForm will be marked as valid too.

ShamPooSham
  • 1,529
  • 2
  • 12
  • 21
0

As Serginho said in the accepted answer, the problem is that updateValueAndValidity method is bottom-up, so it will not validate children controls. A possible solution for that is to write a custom prototype method for the FormGroup class like this:

import { FormGroup, FormControl } from '@angular/forms';

declare module '@angular/forms/forms' {
  interface FormGroup {
    validate(): void;
  }
}

FormGroup.prototype.validate = function(this: FormGroup): void {
  for (const key in this.controls) {
    const formElement = this.get(key);
    if (formElement instanceof FormControl) {
      formElement.updateValueAndValidity();
    } else if (formElement instanceof FormGroup) {
      formElement.validate();
    }
  }
};

validate is a recursive method that call updateValueAndValidity for each leaf (FormControl) of the tree (FormGroup), even in case of nested form groups.

Don't forget to import your module where you need to use the validate method:

import '../core/extensions/formGroup';
ssnake
  • 130
  • 1
  • 10
0

DISCLAIMER: This may be bad practice, but it does what Angular doesn't allow me to do simply. Be warned that this is highly inefficient compared to Angular's way of dealing with validation. Here is how to refresh the whole form validation :

  validateForm(control: AbstractControl) {
    control['controls'].forEach(
      // make sure to pass {onlySelf: true, emitEvent: false} 
      // or it will recurse indefinitely
      control => control.updateValueAndValidity({onlySelf: true, emitEvent: false})
    );

    return null;
  }

And for my usecase I needed that the whole form validity to be updated each time the user changes any field (and not just the validation of the current field or only the fields that were touched, yadi yada) :

this.form.setValidators(this.validateForm);
Leogout
  • 887
  • 1
  • 10
  • 23
0

Try this:

const forms = [this.form1, this.form2, this.form3];

    for (let f of forms) {
      for (let key in f.controls) {
        f.controls[key].updateValueAndValidity();
      }
    }
brett
  • 113
  • 2
  • 14