使用FormControl.setValue()时多次调用ControlValueAccessor.writeValue



考虑以下反应形式设置:

让我们拥有实现ControlValueAccessor接口的自定义SimpleInputComponent。将它添加到用ngIf指令包装的应用程序模板中,这样我们就可以在ngIf表达式更改时重新调用SimpleInputComponent的生命周期。

由于SimpleInputComponent通过formControlName指令绑定到FormControl实例,因此可以通过调用FormControl.setValue方法来更改其值。

我期望FormControl.setValue正好触发SimpleInputComponent.writeValue一次。不幸的是,事实并非如此。

FormControl.setValue调用SimpleInputComponent.writeValue的次数与调用SimpleInputComponent的生命周期的次数一样多。

现在要模拟这个问题,你至少需要两个组件:

SimpleInputComponent:

import { Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControlName } from '@angular/forms';
@Component({
selector: 'simple-input-text',
template: `
<input [name]="formControlName" type="text" 
[(ngModel)]="value"
(input)="onChange($event)"
(blur)="onBlur()" />
`,
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SimpleInputComponent), multi: true }
]
})
export class SimpleInputComponent implements ControlValueAccessor {
private value: string;
constructor() { }
propagateChange: any = () => {};
propagateTouch: any = () => {};
onBlur() {
this.propagateTouch();
}
onChange(event) {
this.propagateChange(event.target.value);
}
writeValue(value: any): void {
console.log('writeValue() called');
this.value = value;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: () => void): void {
this.propagateTouch = fn;
}
setDisabledState?(isDisabled: boolean): void {}
}

应用组件:

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'my-app',
template: `
<div>
<button (click)="hidden = !hidden">{{hidden ? 'show' : 'hide'}}</button>
<button (click)="changeValue()">change value</button>
</div>
<div [formGroup] = "form" *ngIf="!hidden">
<simple-input-text formControlName="test"></simple-input-text>
</div>
`
})
export class AppComponent  {
hidden = false;
form = new FormGroup({
test: new FormControl('')
});
changeValue(): void {
console.log('changeValue() called');
this.form.get('test').setValue('another value');
}
}

AppComponent包含2个按钮:

  1. 显示/隐藏-重新调用SimpleInputComponent的生命周期
  2. 更改值-运行FormControl.setValue通过实现自己的writeValue方法更新SimpleInputComponent

模拟场景:

  • 点击"显示/隐藏"按钮(2n次(
  • 打开控制台,单击"更改值"按钮
  • 检查控制台,查看writeValue被调用(n+1(次(1次来自初始生命周期(

现场样品位于https://stackblitz.com/edit/angular-bgp3kg

所以问题是:我做错了什么吗?

我看到这个修复程序的PR仍然打开。你可以在这里查看。https://github.com/angular/angular/pull/29335.

平均而言,对于解决方案,您可以在SimpleInputComponent中定义ngOnDestroy,如下所示,

ngOnDestroy() {
if (this.ngControl) {
this.ngControl.reset();
this.ngControl.valueAccessor &&
(this.ngControl.valueAccessor.writeValue = () => {});
}
}

最新更新