ControlValueAccessor ngModel не обновляется - PullRequest
0 голосов
/ 13 января 2020

Это простой пользовательский элемент управления формы

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

Используется как показано ниже:

@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

Редактировать:

Актуальную проблему можно увидеть на более простом примере (без внутреннего ввода):

@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

1 Ответ

3 голосов
/ 13 января 2020

Чтобы это работало, вам нужно будет использовать банан в коробке синтаксис для ввода, который находится внутри custom-control.component.ts

custom-control.component.ts

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

Рабочий пример .


Это происходит из-за того, что при вводе во внешний ввод CustomControlComponent '* ControlValueAccessor.writeValue() будет выполнено, что, в свою очередь, обновит внутренний вход.

Давайте разбить его на более мелкие шаги.

1) наберите на внешнем входе

2) сработало обнаружение изменений

3) в конечном итоге будет достигнута директива ngOnChanges из NgModel (которая связана с custom-control), что приведет к обновлению экземпляра FormControl в следующем тике

@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

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.

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

Вот почему ваш внутренний ввод обновляется внешним.


Теперь, почему он не работает по-другому где?

Когда вы вводите внутренний ввод, он вызывает только его onChange функцию, которая будет выглядеть следующим образом:

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

Заглянув внутрь updateControl, вы увидите, что у него есть флаг { emitModelToViewChange: false }. Посмотрев на FormControl.setValue(), мы увидим, что флаг препятствует обновлению внутреннего ввода.

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

{{ value }}

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

{{ i.control.value | json }} <!-- Always Updated -->
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...