Чтобы это работало, вам нужно будет использовать банан в коробке синтаксис для ввода, который находится внутри 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 -->