首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ControlValueAccessor ngModel未更新

ControlValueAccessor ngModel未更新
EN

Stack Overflow用户
提问于 2020-01-13 13:06:19
回答 1查看 7.1K关注 0票数 4

这是简单的自定义窗体控件。

代码语言:javascript
复制
@Component({
  selector: 'app-custom-control',
  template: `
    {{ value }}
    <input [ngModel]="value" (ngModelChange)="onChange($event)">
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomControlComponent),
    multi: true,
  }]
})
export class CustomControlComponent implements ControlValueAccessor {

  private value: any;

  private onChange: (val) => void;
  private onTouch: () => void;

  writeValue(value: any) {
    this.value = value;
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }
}

如下所用:

代码语言:javascript
复制
@Component({
  selector: 'my-app',
  template: `
    <app-custom-control
      [ngModel]="model"
      (ngModelChange)="onChange($event)">
    </app-custom-control>
    <input [ngModel]="model" (ngModelChange)="onChange($event)">
  `,
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  model = 'hello';

  onChange(value) {
    this.model = value;
  }
}

我不理解的是为什么控件的ngModel只是根据外部输入的值进行更新,而不是在使用内部输入的情况下更新呢?这里的实例:https://stackblitz.com/edit/angular-7apjhg

编辑:

简单的例子(没有内部输入)可以看出实际问题:

代码语言:javascript
复制
@Component({
  selector: 'app-custom-control',
  template: `
    {{ value }}
    <button (click)="onChange('new value')">set new value</button>
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomControlComponent),
    multi: true,
  }]
})
export class CustomControlComponent implements ControlValueAccessor {

  value: any;

  onChange: (val) => void;
  onTouched: () => void;

  writeValue(value: any) {
    this.value = value;
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
}

单击自定义控件内的按钮后,将更新父控件上的属性值,但ngModel不会更新。更新示例:https://stackblitz.com/edit/angular-tss2f3

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-01-13 13:48:26

为了使其工作,您必须对驻留在中的输入使用语法中的custom-control.component.ts香蕉

custom-control.component.ts

代码语言:javascript
复制
<input [(ngModel)]="value" (ngModelChange)="onChange($event)">

工作实例

这是因为当您在外部输入输入时,将执行CustomControlComponentControlValueAccessor.writeValue(),这反过来将更新内部输入。

让我们把它分成更小的步骤。

1)输入外部输入

2)触发更改检测。

3) ngOnChangesNgModel指令(绑定到custom-control)将最终到达,这将导致在下一个滴答中更新FormControl实例。

代码语言:javascript
复制
@Directive({
  selector: '[ngModel]:not([formControlName]):not([formControl])',
  providers: [formControlBinding],
  exportAs: 'ngModel'
})
export class NgModel extends NgControl implements OnChanges,
    OnDestroy {
 /* ... */
 ngOnChanges(changes: SimpleChanges) {
    this._checkForErrors();
    if (!this._registered) this._setUpControl();
    if ('isDisabled' in changes) {
        this._updateDisabled(changes);
    }

    if (isPropertyUpdated(changes, this.viewModel)) {
        this._updateValue(this.model);
        this.viewModel = this.model;
    }

  /* ... */

 private _updateValue(value: any): void {
    resolvedPromise.then(
        () => { this.control.setValue(value, { emitViewToModelChange: false }); 
    });
  }
 }
}

4) FormControl.setValue()将调用已注册的更改函数回调,该回调将反过来调用ControlValueAccessor.writeValue

代码语言:javascript
复制
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
    // control -> view
    dir.valueAccessor !.writeValue(newValue);

    // control -> ngModel
    if (emitModelEvent) dir.viewToModelUpdate(newValue);
  });

其中dir.valueAccessor !.writeValue(newValue)将是CustomControlComponent.writeValue函数。

代码语言:javascript
复制
writeValue(value: any) {
    this.value = value;
}

这就是为什么你的内部输入是由外部输入更新的。

现在,为什么它不反过来工作呢?

当您在内部输入输入时,它将只调用及其onChange函数,如下所示:

代码语言:javascript
复制
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函数。

代码语言:javascript
复制
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;
}

查看updateControl内部,您将看到它有{ emitModelToViewChange: false }标志。查看FormControl.setValue(),我们将看到标志阻止内部输入被更新。

代码语言:javascript
复制
setValue(value: any, options: {
    onlySelf?: boolean,
    emitEvent?: boolean,
    emitModelToViewChange?: boolean,
    emitViewToModelChange?: boolean
  } = {}): void {
    (this as{value: any}).value = this._pendingValue = value;

    // Here!
    if (this._onChange.length && options.emitModelToViewChange !== false) {
      this._onChange.forEach(
          (changeFn) => changeFn(this.value, options.emitViewToModelChange !== false));
    }
    this.updateValueAndValidity(options);
  }

实际上,只有内部输入没有更新,但是绑定到该输入的FormControl实例将被更新。通过这样做可以看出这一点:

custom-control.component.html

代码语言:javascript
复制
{{ value }}

<input #i="ngModel" [ngModel]="value" (ngModelChange)="onChange($event)">

{{ i.control.value | json }} <!-- Always Updated -->
票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59717253

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档