Jac*_*ack 8 rxjs typescript angular-material angular
这个问题有一个Stackblitz:stackblitz.com/edit/angular-material-starter-template-8ojscv。
我已经实现了一个包装CodeMirror的自定义 Angular Material FormField。
在我的应用程序组件中,我订阅表单控件上的valueChanges以监听用户键入:
export class AppComponent implements OnInit {
// Custom value accessor for CodeMirror.
control: FormControl = new FormControl('', {updateOn: 'change'});
ngOnInit() {
// Listen for the user typing in CodeMirror.
this.control.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
tap((value: string) => {
console.log(`The user typed "${value}"`);
})
).subscribe();
}
}
Run Code Online (Sandbox Code Playgroud)
我注意到,当使用setValue时,valueChanges Observable 会发出一个值,即使选项对象禁止它:
// This appears to have no effect.
this.control.setValue(value, {
// Prevent the statusChanges and valueChanges observables from
// emitting events when the control value is updated.
emitEvent: false,
}
Run Code Online (Sandbox Code Playgroud)
我的 Stackblitz 演示中的高级流程是:
setValue(0)按钮setValueFormControlemitEvent: falsewriteValue(value: string)组件上调用 ControlValueAccessor 方法( my-input.component)value settervalue值写入 CodeMirror 编辑器this._editor.setValue(value + "")changes 事件被触发(已在 中添加ngAfterViewInit)。this._onChange(cm.getValue()))valueChanges发出更新后的值是的,my-input.component显式调用注册的回调函数,但我预计框架(Angular 或 Angular Material)会尊重emitEvent: false而不发出事件。
自定义 FormField 实现是否有责任实现选项对象,并且如果emitEvent: false已设置,则不调用已注册的回调?
我认为问题来自于codemirrorValueChanged
codemirrorValueChanged(
cm: CodeMirror.Editor,
change: CodeMirror.EditorChangeLinkedList
) {
if (change.origin !== "setValue") {
console.log(`_onChange(${this.value})`);
this._onChange(cm.getValue());
}
}
Run Code Online (Sandbox Code Playgroud)
但首先,让我们看看发生了什么FormControl.setValue():
setValue(value: any, options: {
onlySelf?: boolean,
emitEvent?: boolean,
emitModelToViewChange?: boolean,
emitViewToModelChange?: boolean
} = {}): void {
(this as {value: any}).value = this._pendingValue = value;
if (this._onChange.length && options.emitModelToViewChange !== false) {
this._onChange.forEach(
(changeFn) => changeFn(this.value, options.emitViewToModelChange !== false));
}
this.updateValueAndValidity(options);
}
Run Code Online (Sandbox Code Playgroud)
无论您使用反应式表单还是模板表单,都必须设置每个控件,为此我们有函数_setupControl(NgModel,FormControlName),它具有不同的实现,具体取决于指令,但在每种情况下它最终都会称呼setUpControl:
export function setUpControl(control: FormControl, dir: NgControl): void {
if (!control) _throwError(dir, 'Cannot find control with');
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
control.validator = Validators.compose([control.validator!, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator!, dir.asyncValidator]);
// `writeValue`: MODEL -> VIEW
dir.valueAccessor!.writeValue(control.value);
setUpViewChangePipeline(control, dir);
setUpModelChangePipeline(control, dir);
setUpBlurPipeline(control, dir);
if (dir.valueAccessor!.setDisabledState) {
/* ... */
}
/* ... */
}
Run Code Online (Sandbox Code Playgroud)
ThesetUpViewChangePipeline是被调用的ControlValueAccessor地方:registerOnChange
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
dir.valueAccessor!.registerOnChange((newValue: any) => {
control._pendingValue = newValue;
control._pendingChange = true;
control._pendingDirty = true;
// `updateControl` - update value from VIEW to MODEL
// e.g `VIEW` - an input
// e.g `MODEL` - [(ngModel)]="componentValue"
if (control.updateOn === 'change') updateControl(control, dir);
});
}
Run Code Online (Sandbox Code Playgroud)
这setUpModelChangePipeline是填充_onChange数组(来自setValue代码片段)的位置:
function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
// control -> view
dir.valueAccessor!.writeValue(newValue);
// control -> ngModel
if (emitModelEvent) dir.viewToModelUpdate(newValue);
});
}
Run Code Online (Sandbox Code Playgroud)
因此,这就是emitModelToViewChange标志 (from options.emitModelToViewChange !== false) 很重要的地方。
接下来,我们有updateValueAndValidity,这是valueChanges和statusChanges主题发出的地方:
updateValueAndValidity(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
this._setInitialStatus();
this._updateValue();
if (this.enabled) {
// In case of async validators
this._cancelExistingSubscription();
// Run sync validators
(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);
}
}
Run Code Online (Sandbox Code Playgroud)
因此,我们可以得出结论,问题并非源于FormControl.setValue(val, { emitEvent: false }).
updateValueAndValidity在调用之前,我们看到_onChange函数将首先被调用。同样,这样的函数看起来像这样:
// From `setUpModelChangePipeline`
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
// control -> view
dir.valueAccessor!.writeValue(newValue);
// control -> ngModel
if (emitModelEvent) dir.viewToModelUpdate(newValue);
});
Run Code Online (Sandbox Code Playgroud)
在我们的例子中,valueAccessor.writeValue看起来像这样:
writeValue(value: string): void {
console.log(`[ControlValueAccessor] writeValue(${value})`);
// Updates the Material UI value with `set value()`.
this.value = value;
}
Run Code Online (Sandbox Code Playgroud)
这将调用设置器:
set value(value: string | null) {
console.log(`[MatFormFieldControl] set value(${value})`);
if (this._editor) {
this._editor.setValue(value + "");
this._editor.markClean();
// Postpone the refresh() to after CodeMirror/Browser has updated
// the layout according to the new content.
setTimeout(() => {
this._editor.refresh();
}, 1);
}
this.stateChanges.next();
}
Run Code Online (Sandbox Code Playgroud)
由于_editor.setValue,该onChanges事件将发生并被codemirrorValueChanged调用:
codemirrorValueChanged(
cm: CodeMirror.Editor,
change: CodeMirror.EditorChangeLinkedList
) {
if (change.origin !== "setValue") {
console.log(`_onChange(${this.value})`);
this._onChange(cm.getValue());
}
}
Run Code Online (Sandbox Code Playgroud)
_onChange调用这个回调有什么作用:
// from `setUpViewChangePipeline`
dir.valueAccessor!.registerOnChange((newValue: any) => {
control._pendingValue = newValue;
control._pendingChange = true;
control._pendingDirty = true;
if (control.updateOn === 'change') updateControl(control, dir);
});
Run Code Online (Sandbox Code Playgroud)
并updateControl会打电话control.setValue,但没有 emitEvent: false:
function updateControl(control: FormControl, dir: NgControl): void {
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
dir.viewToModelUpdate(control._pendingValue);
control._pendingChange = false;
}
Run Code Online (Sandbox Code Playgroud)
因此,这应该可以解释当前的行为。
我在调试时发现的一件事是,这change是一个数组,而不是一个对象。
因此,一个可能的解决方案是:
codemirrorValueChanged(
cm: CodeMirror.Editor,
change: CodeMirror.EditorChangeLinkedList
) {
if (change[0].origin !== "setValue") {
console.log(`_onChange(${this.value})`);
this._onChange(cm.getValue());
}
}
Run Code Online (Sandbox Code Playgroud)
我试图在Angular Forms 的彻底探索中解释这些概念以及 Angular Forms 内部结构是如何工作的。
| 归档时间: |
|
| 查看次数: |
2635 次 |
| 最近记录: |