Angular6、this.formGroup.updateValueAndValidity()不能正常工作

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

我正在尝试根据特定条件在 formGroup 控件中添加和删除验证器。

当我通过 formGroup.updateValueAndValidity() 为整个表单更新验证器时,它没有更新,好像我专门为每个控件申请,即 formGroup.get('formControl').updateValueAndValidity(),它正在工作,但我必须写我希望每个控件都不是正确的方法。我做错了什么?

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.myForm.updateValueAndValidity();

这不起作用。

updateValueAndValidity() 是自下而上的,因此如果您在控件上调用此方法,它将仅检查此控件及其父控件的验证,而不是它们的子控件。

有关详细信息,请参阅 github 上的 AbstractControl#updateValueAndValidity 以了解其工作原理。

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

我上周也偶然发现了这个问题,我得出的结论是这是预期的行为。 docs 声明如下:

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

请注意,它说的是 "ancestor" 而不是 "descendants"。这意味着当您在 control1control2 上有 运行 updateValueAndValidity() 并且它们都有效时,myForm 也将被标记为有效。

正如 Serginho 在接受的答案中所说,问题在于 updateValueAndValidity 方法是自下而上的,因此它不会验证子控件。 一个可能的解决方案是为 FormGroup class 编写自定义原型方法,如下所示:

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 是一种递归方法,它为树 (FormGroup) 的每个叶子 (FormControl) 调用 updateValueAndValidity,即使在嵌套表单组的情况下也是如此。

不要忘记在需要使用验证方法的地方导入模块:

import '../core/extensions/formGroup';

免责声明:这可能是不好的做法,但它做了 Angular 不允许我做的事情。请注意,与 Angular 处理验证的方式相比,这是非常低效的。以下是刷新整个表单验证的方法:

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

对于我的用例,我需要在每次用户更改任何字段时更新整个表单有效性(而不仅仅是当前字段的验证或仅被触摸的字段,yadi yada):

this.form.setValidators(this.validateForm);

试试这个:

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

    for (let f of forms) {
      for (let key in f.controls) {
        f.controls[key].updateValueAndValidity();
      }
    }

我喜欢@ssnake 的解决方案,但它缺少 FormArray 支持。稍微修改了一下,也做了class-agnostic,只是检查是否有控件,并为每个控件触发验证,无论是FormControl、FormGroup还是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();
      }
    }
  }
};