gpt4 book ai didi

angular - 自定义 Angular FormField 在配置为不触发时发出事件

转载 作者:行者123 更新时间:2023-12-04 13:34:56 37 4
gpt4 key购买 nike

这个问题有一个 Stackblitz:stackblitz.com/edit/angular-material-starter-template-8ojscv .
我已经实现了 custom Angular Material FormField包裹 CodeMirror .
在我的 应用程序我订阅的组件 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();
}
}
我注意到在使用 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,
}
my Stackblitz demo中的高层流程是:
  • 点击 setValue(0)按钮
  • 应用程序组件调用 setValue在 FormControl 上使用 emitEvent: false
  • ControlValueAccessor writeValue(value: string)在自定义 FormField 组件 ( my-input.component )
  • 上调用方法
  • 写入值委托(delegate)给 MatFormFieldControl value二传手
  • MatFormFieldControl value setter 使用 this._editor.setValue(value + "") 将值写入 CodeMirror 编辑器
  • changes CodeMirror event被触发(它是在 ngAfterViewInit 中添加的)。
  • 使用更新后的值 ( this._onChange(cm.getValue()) )
  • 调用注册的回调函数
  • valueChanges Observable 发出更新后的值

  • 是的, my-input.component显式调用注册的回调函数,但我曾期望框架(Angular 或 Angular Material)能够兑现 emitEvent: false并且不发出事件。
    如果 emitEvent: false,自定义FormField 实现是否有责任实现选项对象而不调用注册的回调?设置了吗?

    最佳答案

    我认为问题来自codemirrorValueChanged

    codemirrorValueChanged(
    cm: CodeMirror.Editor,
    change: CodeMirror.EditorChangeLinkedList
    ) {
    if (change.origin !== "setValue") {
    console.log(`_onChange(${this.value})`);
    this._onChange(cm.getValue());
    }
    }
    但首先,让我们看看 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);
    }
    是否使用 响应式(Reactive) 模板表格 ,必须设置每个控件,为此我们有 _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) {
    /* ... */
    }

    /* ... */
    }
    setUpViewChangePipeline 是哪里 ControlValueAccessorregisterOnChange将被称为:
    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);
    });
    }
    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);
    });
    }
    所以,这就是 emitModelToViewChange标志(来自 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);
    }
    }
    因此,我们可以得出结论,问题并非源于 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);
    });
    在我们的例子中, valueAccessor.writeValue看起来像这样:
    writeValue(value: string): void {
    console.log(`[ControlValueAccessor] writeValue(${value})`);
    // Updates the Material UI value with `set value()`.
    this.value = value;
    }
    这将调用setter:
    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();
    }
    由于 _editor.setValue , onChanges事件将发生和 codemirrorValueChanged将被调用:
    codemirrorValueChanged(
    cm: CodeMirror.Editor,
    change: CodeMirror.EditorChangeLinkedList
    ) {
    if (change.origin !== "setValue") {
    console.log(`_onChange(${this.value})`);
    this._onChange(cm.getValue());
    }
    }
    什么 _onChange是否调用此回调:
    // from `setUpViewChangePipeline`
    dir.valueAccessor!.registerOnChange((newValue: any) => {
    control._pendingValue = newValue;
    control._pendingChange = true;
    control._pendingDirty = true;

    if (control.updateOn === 'change') updateControl(control, dir);
    });
    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;
    }
    因此,这应该可以解释当前的行为。

    我在调试时发现的一件事是 change是一个数组,而不是一个对象。
    因此,一个可能的解决方案是:
    codemirrorValueChanged(
    cm: CodeMirror.Editor,
    change: CodeMirror.EditorChangeLinkedList
    ) {
    if (change[0].origin !== "setValue") {
    console.log(`_onChange(${this.value})`);
    this._onChange(cm.getValue());
    }
    }

    我试图在 A thorough exploration of Angular Forms 中解释这些概念以及 Angular Forms 内部是如何工作的。 .

    关于angular - 自定义 Angular FormField 在配置为不触发时发出事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62762453/

    37 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com