如何解决嵌套反应式表单组件的更改检测问题



我有以下(简化的(场景:

  • 具有(反应式(表单的组件,其中嵌套组件充当表单控件
  • 其中一个嵌套组件称为SelectComponent,它提供<select>标签
  • SelectComponent实现NG_VALUE_ACCESSORNG_VALIDATORS
  • 父组件将选择选项作为输入字段提供给SelectComponent

到目前为止,这工作得很好,但当我想根据以下内容自动更新内部SelectComponent中的行为时,我遇到了问题:

  • 如果有多个选项或不需要控制,则不执行任何操作
  • 如果只有一个选项,并且需要控制,则自动选择(仅(选项

为了实现上述目的,我实现了ngOnChanges事件。

以下是一个关于Stacklitz的最简单的例子,它几乎正在发挥作用:https://stackblitz.com/edit/angular-ohdb71?file=src%2Fapp%2Fselect%2Fselect.component.ts

但是,我在主要组件中的更改检测方面遇到了问题。正如您所看到的,主组件不会自动将user.value更新为Walter,即使内部SelectComponent已经设置了这个值,并且还向外部组件宣布了这个值。因此,主组件认为该值未设置,并将表单控件标记为错误(必需(。

奇怪的是,每当我将代码<app-select [formControlName]="'user'" [options]="userOptions"></app-select>移到文件顶部时,代码都能工作(没有ExpressionChangedAfterItHasBeenCheckedError(。

我如何在SelectComponent中解决这个问题,使外部组件不必再实现任何逻辑?

TLDR:您可以在这里找到您的工作示例:https://stackblitz.com/edit/angular-f8fiux

要使其发挥作用,需要执行以下几个步骤:

修改writeValue方法。writeValue是自定义组件的输入,因此不应调用onChange方法(即组件的输出(,否则可能导致不正确的交互。

writeValue(value: any): void {
this.selectFormControl.setValue(value, { emitEvent: false });
}

这里传递CCD_ 14以不触发CCD_。为了理解价值访问器的逻辑,我推荐这篇文章。


修改ngOnChange方法。它应该设置组件的内部值(通过writeValue(,然后将其发送给外部使用者(通过onChange(。对于onChange,您必须安排微任务(Promise.resolve(或宏任务(setTimeout(,否则数据将在相同的更改检测周期中发出,导致ExpressionChangedAfterItHasBeenCheckedError

ngOnChanges(simpleChanges: SimpleChanges): void {
if (simpleChanges.options) {
if (this.options.length === 1) {
if (this.selectFormControl.value !== this.options[0]) {
this.writeValue(this.options[0]);
setTimeout(() => {
this.onChange(this.options[0]);
});
}
} else {
this.writeValue(null);
}
}
}

最好不要混合反应式表单和模板驱动的表单,所以我建议您在模板中去掉(ngModelChange)="onChange($event)",因为它是异步数据流,需要更复杂的处理。相反,您可以在构造函数中添加一个简单的订阅(不要忘记取消订阅,这里我省略了它(

constructor(private cdr: ChangeDetectorRef) {
this.selectFormControl.valueChanges.subscribe((value) => {
this.onChange(value);
});
}

您可以在Angular文档中找到有关asyncsync流的详细信息

最新更新