首页 > 解决方案 > 更新 NgModel 时,Angular ngModelChange 迟到了

问题描述

我正在使用 angular 8 制作指令来进行一些处理并将文本转换为大写。简化代码如下:

html:

<input class="form-control" id="label" name="label" required myDirective>

指示:

import { Directive, HostListener } from '@angular/core';
import { NgControl } from '@angular/forms';

@Directive({
  selector: '[myDirective]'
})
export class Mydirective {
  constructor(private control: NgControl) { }

  processInput(value: any) {
     // do some formatting
     return value.toUpperCase();
  }

  @HostListener('ngModelChange', ['$event'])
  ngModelChange(value: any) {
     this.control.valueAccessor.writeValue(this.processInput(value));
  }
}

现在,视图已正确更新,但模型晚了一步。例如:如果输入文本显示“AAAA”,那么ng-reflect-model将显示“AAAa”。

我已经在 stackblitz 中重现了错误:Stackblitz 中重现的错误

知道我哪里错了吗?

之前谢谢!

标签: angular

解决方案


TLDR

堆栈闪电战

我的指令.directive.ts

/* ... */

ngOnInit () {
  const initialOnChange = (this.ngControl.valueAccessor as any).onChange;

  (this.ngControl.valueAccessor as any).onChange = (value) => initialOnChange(this.processInput(value));
}

/* ... */

@HostListener('ngModelChange', ['$event'])
ngModelChange(value: any) {
  this.ngControl.valueAccessor.writeValue(this.processInput(value));
}

详细解答

让我们看看为什么它最初不起作用。

Angular 具有某些元素的默认值访问器,例如 forinput type='text'input type='checkbox'...

AControlValueAccessor是VIEW 层和 MODEL 层之间的中间人。当用户输入输入时,VIEW 通知ControlValueAccessor,它负责通知 MODEL。

例如,当input事件发生时,将调用的onChange方法。以下是每个的样子:ControlValueAccessor onChange ControlValueAccessor

function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
  dir.valueAccessor!.registerOnChange((newValue: any) => {
    control._pendingValue = newValue;
    control._pendingChange = true;
    control._pendingDirty = true;

    if (control.updateOn === 'change') updateControl(control, dir);
  });
}

魔法发生在updateControl

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;
}

dir.viewToModelUpdate(control._pendingValue);是调用ngModelChange自定义指令中的事件的原因。这意味着模型值是来自输入的值(小写)。并且因为ControlValueAccessor.writeValue 将值写入到 VIEW 中,所以VIEW 的值和 MODEL 的值之间会有一个延迟。

值得一提的是,它将FormControl.setValue(val)写入VIEW 和 MODEL这两个层,但如果我们要使用它,将会有一个无限循环,因为内部调用(因为必须更新 MODEL)和调用.valsetValue()viewToModelUpdateviewToModelUpdatesetValue()

让我们看看一个可能的解决方案:

ngOnInit () {
  const initialOnChange = (this.ngControl.valueAccessor as any).onChange;

  (this.ngControl.valueAccessor as any).onChange = (value) => initialOnChange(this.processInput(value));
}

使用这种方法,您在 VIEW 层修改数据,然后将其发送到ControlValueAccessor.

我们可以确定它onChange存在于每个内置的ControlValueAccessor.

搜索结果

如果你要创建一个自定义的,只要确保它有一个onChange属性。TypeScript 可以帮助你。

如果您想了解更多关于 Angular 的内部结构的信息@angular/forms,我建议您阅读Angular Forms 的彻底探索


推荐阅读