具有异步验证程序的控件在另一个控件上调用 updateValueAndValidity 时将其状态更改为挂起



stackblitz example

重现步骤:

  1. 打开控制台以查看日志
  2. 在输入(名称控制)中键入内容 - 异步验证完成后,名称控件发出状态更改事件。
  3. 选中复选框
  4. 取消选中该复选框
  5. 名称控件处于挂起状态

现在,问题来了:为什么名称控件会更改其状态?hello.component 中不相关控件的 updateValueAndValidity() 如何影响名称控件状态会发生什么?

从 updateValueAndValidity 的文档来看,它应该只更新控件及其祖先,而不是兄弟姐妹......请帮助我理解这一点。

您注意到的行为的原因非常微妙,没有记录在案。

响应式表单是使用FormControlFormArrayFormGroup对象构建的。这些类公开了定义和执行验证程序、禁用控件和更改值的功能。如果您从未使用指令([formControl][formControlName][formGroup]等)将这些控件中的任何一个绑定到表单,您将看到验证器期望/预期的行为。

但是,如果您有异步验证器并在这些验证器运行时将控件绑定到窗体,则它们将"卡"在PENDING中。底层status将在异步验证程序完成后更新,但statusChanges主体不会发出事件,直到其他操作导致其运行。

这发生在上面使用的指令的绑定中。与 Angular 中的所有指令一样,ngOnChanges钩将在实例化时调用。这是ngOnChanges的实现 对于form_group_directive.ts.

this._checkFormPresent();
if (changes.hasOwnProperty('form')) {
this._updateValidators();
this._updateDomValue();
this._updateRegistrations();
this._oldForm = this.form;
}

上面重要的一行是this._updateDomValue();。对updateDomValue()的调用将依次调用this.form._updateTreeValidity({emitEvent: false});。然后,对_updateTreeValidity()的调用将调用this.updateValueAndValidity({onlySelf: true, emitEvent: opts.emitEvent});。然后,对updateValueAndValidity()的调用将执行以下操作:

this._setInitialStatus();
this._updateValue();
if (this.enabled) {
this._cancelExistingSubscription();
(this as {errors: ValidationErrors | null}).errors = this._runValidator();
(this as {status: FormControlStatus}).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<FormControlStatus>).emit(this.status);
}
if (this._parent && !opts.onlySelf) {
this._parent.updateValueAndValidity(opts);
}

这里发生了很多事情,但这里是重要的部分。首先,呼吁this._cancelExistingSubscription()。这将取消任何当前正在运行的异步验证程序,使控件处于PENDING状态。接下来,将调用对this._runAsyncValidator(opts.emitEvent)的调用。这将触发异步验证器再次运行,并进行重要更改。传递给函数的opts.emitEvent值现在将false这可以从前面对_updateTreeValidity()的调用中看出。由于验证器的新执行不会发出事件。验证器将完成,status设置为适当的结果,但没有事件,您将不会收到来自statusChanges的状态更改。

这个示例演示了导致您看到的问题的原因。如果在任何时候都可以用emitEvent = true调用updateValueAndValidity()statusChanges主题将"卡"在PENDING中。

现在了解您问题的细节。

为什么名称控件会更改其状态?

我还没有通过您的示例完全调试,上面的示例是您所看到的原因之一。下面可能是另一个:

  1. 你用app.component.ts打电话给this.form.setControl(FormFields.Documents, control);.
  2. setControl()打电话给this._onCollectionChange().[formGroup]指令将其设置为this._updateDomValue()
  3. _updateDomValue()打电话给this.form._updateTreeValidity({emitEvent: false});.这在我上面的例子中是一样的。请注意,emitEventfalse
  4. _updateTreeValidity()呼吁_updateTreeValidity()它的所有孩子。这包括您的"名称"控件。

hello.component 中不相关控件的 updateValueAndValidity() 如何影响名称控件状态会发生什么?

我认为这不会影响"名称"控件状态。我看到this.form.valueChanges的控制台输出,但没有看到this.form.get(FormFields.Name).statusChanges的控制台输出。对valueChanges的订阅正在输出"name"控件的状态,但这是因为窗体的值已更改。验证器永远不会运行,因为它正在使用valueChanges这就是为什么它留在PENDING.这可能是您想要的,也可能不是您想要的。异步验证器已在输入的值更改时触发。如果你想确保它不会进行太多的HTTP调用,我可能会建议使用来自rxjs的timer()或将updateOn设置为blur

计时器示例:

nameValidatorAsync(): AsyncValidatorFn {
return (control) =>
timer(300).pipe(
switchMap(() =>
this.http.get(FAKE_URL).pipe(map(() => Math.random() > 0.5))
),
map((isValid) => (isValid ? null : { invalidName: true })),
first()
);
}

updateOn示例

...
[FormFields.Name]: ['', {
validators: [],
asyncValidators: [this.nameValidatorAsync()],
updateOn: 'blur'
}]
...

关于如何解决这些项目,我没有任何很好的答案。我也不认为它们会很快得到解决。此票证提供了一些有关创建状态可观察量的建议,可能会对您有所帮助。

最新更新