Formcontrol不同值,通过valueAccessor.writeValue()设置值后保持不变

Formcontrol different value, stays unchanged after setting value via valueAccessor.writeValue()

问题与Formcontrol invalid state unchanged after setting value via valueAccessor.writeValue()相同。但是没有解决。你可以回复他post.

我的情况是这样。我有 phone 个指令:

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

@Directive({
  selector: '[phonemask]'
})
export class PhoneDirective {

  constructor(public ngControl: NgControl) { }

  @HostListener('ngModelChange', ['$event'])
  onModelChange(event){
    this.onInputChange(event, false);
  }

  @HostListener('keydown.backspace', ['$event'])
  keydownBackspace(event){
    this.onInputChange(event.target.value, true);
  }

  onInputChange(event, backspace){
    let newVal = event.replace(/\D/g, '');
    if (backspace && newVal.length <= 6) {
      newVal = newVal.substring(0, newVal.length - 1);
    }
    if (newVal.length === 0) {
      newVal = '';
    } else if (newVal.length <= 3) {
      newVal = newVal.replace(/^(\d{0,3})/, '');
    } else if (newVal.length <= 6) {
      newVal = newVal.replace(/^(\d{0,3})(\d{0,3})/, '-');
    } else if (newVal.length <= 9) {
      newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,3})/, '--');
    } else {
      newVal = newVal.substring(0, 9);
      newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,3})/, '--');
    }
    this.ngControl.valueAccessor.writeValue(newVal);
    console.log(newVal);
    console.log(this.ngControl.value)
  }
}

问题是在某些情况下console.log(newVal); console.log(this.ngControl.value) 有不同的值。

例如

111-1
1111

111-111-111
111-111-1111

引自this answer:

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

一个ControlValueAccessor是VIEW层和MODEL层之间的中间人。当用户键入输入时,VIEW 通知 ControlValueAccessor,后者负责通知 MODEL。

例如,当input事件发生时,ControlValueAccessoronChange方法将被调用。 Here's how 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)会将val写入两个层,VIEW和MODEL层,但是如果我们使用这个,会有无限循环,因为 setValue() 内部调用 viewToModelUpdate(因为必须更新模型),而 viewToModelUpdate 调用 setValue()


因此,一个可能的解决方案是将此代码段添加到您的指令中:

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

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

@HostListener('ngModelChange', ['$event'])
onModelChange(event){
  this.ngControl.valueAccessor.writeValue(this.makeChangesToInput(event, false));
}

@HostListener('keydown.backspace', ['$event'])
keydownBackspace(event){
  this.ngControl.control.setValue(this.makeChangesToInput(event, true));

  // if you want the `ngModelChange` handler from above to be called
  // this.ngControl.control.setValue(this.makeChangesToInput(event, true), { emitViewToModelChange: true }); 
}

makeChangesToInput(value, backspace){
  let newVal = event.replace(/\D/g, '');
  if (backspace && newVal.length <= 6) {
    newVal = newVal.substring(0, newVal.length - 1);
  }
  if (newVal.length === 0) {
    newVal = '';
  } else if (newVal.length <= 3) {
    newVal = newVal.replace(/^(\d{0,3})/, '');
  } else if (newVal.length <= 6) {
    newVal = newVal.replace(/^(\d{0,3})(\d{0,3})/, '-');
  } else if (newVal.length <= 9) {
    newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,3})/, '--');
  } else {
    newVal = newVal.substring(0, 9);
    newVal = newVal.replace(/^(\d{0,3})(\d{0,3})(\d{0,3})/, '--');
  }

  return newVal;
}

要点是您应该在 VIEW 层对输入值执行任何更改,然后再将更改后的值发送到 ControlValueAccessor