47

In my Angular 4 app, I have a form with several controls.

At some points I need to force the update of their validity, so I'm doing:

this.form.get('control1').updateValueAndValidity();
this.form.get('control2').updateValueAndValidity();
this.form.get('control3').updateValueAndValidity();
// and so on....

and then:

this.form.updateValueAndValidity();

this works fine.

However I was wondering if there is a better way to accomplish the same thing, by just calling one method on the parent form.

According to its documentation, the updateValueAndValidity() method:

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

but in my case I need to update the value and validity of its descendants. So I can get rid of many lines of code.

Francesco Borzi
  • 56,083
  • 47
  • 179
  • 252
  • Did you try something to see whether it *does* update its descendants? – jonrsharpe Oct 28 '17 at 14:29
  • something like ... ? – Francesco Borzi Oct 28 '17 at 14:58
  • ...not calling the descendants' methods directly first? I'd have thought `updateValueAndValidity` *would* also apply to child forms. – jonrsharpe Oct 28 '17 at 15:03
  • yes of course, and it does not work – Francesco Borzi Oct 28 '17 at 15:07
  • 1
    Interesting question. I think it's not available out of the box, there's same issue in this case with `markAsDirty`: https://github.com/angular/angular/issues/11774 I think you need to do some workaround and iterate the object properties in parent and use `updateValueAndValidity` on each control. Hmm. It would be handy tho that it would be available to just call it on the parent. – AT82 Oct 28 '17 at 18:57
  • See related question for some options: https://stackoverflow.com/questions/42235156/angular-2-iterate-over-reactive-form-controls – mc01 Apr 14 '18 at 02:11

6 Answers6

17

I had the same situation for me to update FormGroup | FormArray at nested level controls.

check this out(worked for me):

/**
 * Re-calculates the value and validation status of the entire controls tree.
 */
function updateTreeValidity(group: FormGroup | FormArray): void {
  Object.keys(group.controls).forEach((key: string) => {
    const abstractControl = group.controls[key];

    if (abstractControl instanceof FormGroup || abstractControl instanceof FormArray) {
      updateTreeValidity(abstractControl);
    } else {
      abstractControl.updateValueAndValidity();
    }
  });
}
Ravi Anand
  • 5,106
  • 9
  • 47
  • 77
  • 1
    Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ [key: string]: AbstractControl; } | AbstractControl[]'. No index signature with a parameter of type 'string' was found on type '{ [key: string]: AbstractControl; } | AbstractControl[]'.ts(7053) – farahm Jul 06 '23 at 23:08
14

It is not possible at the moment to update the descendants of an AbstractControl (FormGroup, ...) with the AbstractControl itself. Maybe in a future release it will be possible.

https://github.com/angular/angular/issues/6170

https://github.com/angular/angular/issues/22166

update: a pull request is already open https://github.com/angular/angular/pull/19829

Gerros
  • 688
  • 11
  • 20
10

I solved my issue, which was similar to yours, by recursing the controls and manually triggering the update. Probably this is not an optimal solution:

private triggerValidation(control: AbstractControl) {
    if (control instanceof FormGroup) {
        const group = (control as FormGroup);

        for (const field in group.controls) {
            const c = group.controls[field];

            this.triggerValidation(c);
        }
    }
    else if (control instanceof FormArray) {
        const group = (control as FormArray);

        for (const field in group.controls) {
            const c = group.controls[field];

            this.triggerValidation(c);
        }
    }

    control.updateValueAndValidity({ onlySelf: false });
}
Yennefer
  • 5,704
  • 7
  • 31
  • 44
2
validateFormFields(fields) {
    try {
      Object.entries(fields.controls).forEach((control: any) => {
        if (typeof control[0] == 'Array' 
        ) {
          this.validateFormFields(control[1]);
        } else {
          if (control[1].controls) {
            Object.entries(control[1].controls).forEach((c: any) => {
              c[1].touched = true;
            });
          } else {
            control[1].touched = true;
          }

        }
      });
    } catch (e) {
      console.log(e);
    }
  }
2

If you need to trigger the validation on the control and its descendants, there is a workaround to call updateTreeValidity while it's not part of the public API:

(<any>form)._updateTreeValidity({ emitEvent: false });

The issue is being tracked here: https://github.com/angular/angular/issues/6170

Guerric P
  • 30,447
  • 6
  • 48
  • 86
1

I ran into the same problem, one of the issues was that listening to the valueChanges observable and triggering updateValueAndValidity to run my validators caused an endless loop, so to resolve (and somewhat hacky ?) I created the component variable validityCycleFired and initialized as false, that I toggle and it prevents a runaway loop:

const fireValidityCheck = () => {
     if (this.validityCycleFired) return;
     this.validityCycleFired = true;
     this.passwordForm.get('old_password').updateValueAndValidity();
     this.passwordForm.get('new_password').updateValueAndValidity();
     this.passwordForm.get('confirm_new_password').updateValueAndValidity();
     setTimeout(() => this.validityCycleFired = false, 15);
     return;
};
this.passwordForm.valueChanges.subscribe(fireValidityCheck);
brooklynDadCore
  • 1,309
  • 8
  • 13