Angular 6, this.formGroup.updateValueAndValidity() not working properly
Asked Answered
Y

7

21

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.

Yulma answered 13/11, 2019 at 8:26 Comment(2)
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.Cadmium
How about calling this.myForm.markAllAsTouched(); instead?Reprimand
D
34

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);
    }
  }
Dinesh answered 13/11, 2019 at 8:46 Comment(2)
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???Saros
@Saros I think you even have to call it on every child. I agree that this is prety stupidSammysamoan
Y
10

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.

Yila answered 13/11, 2019 at 8:47 Comment(0)
S
8

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';
Smokejumper answered 22/6, 2020 at 13:57 Comment(0)
M
2

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);
Mack answered 7/10, 2020 at 8:18 Comment(0)
R
1

I liked @ssnake's solution, but it lacks FormArray support. Modified it a bit, also made it class-agnostic, just checking if there are controls and firing validate for every of them, no matter is it FormControl, FormGroup or FormArray:

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

AbstractControl.prototype.validate = function(): void {
  this.updateValueAndValidity();

  if ('controls' in this) {
    for (const key in this.controls) {
      if (this.controls.hasOwnProperty(key)) {
        this.get(key).validate();
      }
    }
  }
};
Rubble answered 3/2, 2022 at 16:6 Comment(0)
P
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();
      }
    }
Paleface answered 12/3, 2021 at 13:42 Comment(0)
T
0

Use the following method to validate any FormControl, FormGroup and FormArray.

UpdateValueAndValidity<T extends AbstractControl>(control: T): void {
  try {
    if (control instanceof FormGroup) {
      control.updateValueAndValidity()

      const controls = control.controls
      Object.keys(controls).forEach(key => {
        this.UpdateValueAndValidity(controls[key])
      })
    }
    else if (control instanceof FormArray) {
      control.updateValueAndValidity()

      control.controls.forEach(formControl => {
        this.UpdateValueAndValidity(formControl)
      })
    }
    else if (control instanceof FormControl) {
      control.updateValueAndValidity()
    }
    else {
      throw new Error('Error: unexpected control value')
    }
  } catch (error) {
    console.log(error)
  }
}
Trotline answered 21/2, 2023 at 11:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.