[ngDefaultControl]
Сторонні органи управління вимагають ControlValueAccessor
функціонування з кутовими формами. Багато з них, як і полімерні <paper-input>
, поводяться як <input>
рідний елемент і тому можуть використовувати його DefaultValueAccessor
. Додавання ngDefaultControl
атрибуту дозволить їм використовувати цю директиву.
<paper-input ngDefaultControl [(ngModel)]="value>
або
<paper-input ngDefaultControl formControlName="name">
Тож це головна причина, чому цей втручання було введено.
Його називали ng-default-control
атрибутом в альфа-версіях angular2 .
Так ngDefaultControl
є одним із селекторів для директиви DefaultValueAccessor :
@Directive({
selector:
'input:not([type=checkbox])[formControlName],
textarea[formControlName],
input:not([type=checkbox])[formControl],
textarea[formControl],
input:not([type=checkbox])[ngModel],
textarea[ngModel],
[ngDefaultControl]', <------------------------------- this selector
...
})
export class DefaultValueAccessor implements ControlValueAccessor {
Що це означає?
Це означає, що ми можемо застосувати цей атрибут до елемента (наприклад, полімерний компонент), який не має власного доступу. Тож цей елемент буде приймати поведінку, DefaultValueAccessor
і ми можемо використовувати цей елемент з кутовими формами.
В іншому випадку ви повинні забезпечити власну реалізацію ControlValueAccessor
ControlValueAccessor
Кутові документи
ControlValueAccessor виступає мостом між API кутових форм та нативним елементом у DOM.
Запишемо наступний шаблон у простій програмі angular2:
<input type="text" [(ngModel)]="userName">
Щоб зрозуміти, як input
поводиться наше вище, нам потрібно знати, які директиви застосовуються до цього елемента. Тут кутовий дає деякий натяк на помилку:
Невідпрацьоване відхилення обіцянки: Помилки аналізу шаблону: Неможливо прив’язати до 'ngModel', оскільки це не відома властивість 'input'.
Гаразд, ми можемо відкрити SO та отримати відповідь: імпортувати FormsModule
до вашого @NgModule
:
@NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
Ми імпортували його та все працює за призначенням. Але що відбувається під капотом?
FormsModule експортує для нас наступні директиви:
@NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
Після деякого розслідування ми зможемо виявити, що до нашої буде застосовано три директиви input
1) NgControlStatus
@Directive({
selector: '[formControlName],[ngModel],[formControl]',
...
})
export class NgControlStatus extends AbstractControlStatus {
...
}
2) NgModel
@Directive({
selector: '[ngModel]:not([formControlName]):not([formControl])',
providers: [formControlBinding],
exportAs: 'ngModel'
})
export class NgModel extends NgControl implements OnChanges,
3) DEFAULT_VALUE_ACCESSOR
@Directive({
selector:
`input:not([type=checkbox])[formControlName],
textarea[formControlName],
input:not([type=checkbox])formControl],
textarea[formControl],
input:not([type=checkbox])[ngModel],
textarea[ngModel],[ngDefaultControl]',
,,,
})
export class DefaultValueAccessor implements ControlValueAccessor {
NgControlStatus
Директива просто маніпулює класи , як ng-valid
, ng-touched
, ng-dirty
і ми можемо опустити його тут.
DefaultValueAccesstor
надає NG_VALUE_ACCESSOR
маркер у масиві постачальників:
export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
...
@Directive({
...
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {
NgModel
директива вводить в NG_VALUE_ACCESSOR
маркер конструктора, який був оголошений на тому ж хост-елементі.
export NgModel extends NgControl implements OnChanges, OnDestroy {
constructor(...
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
У нашому випадку NgModel
буде вводити DefaultValueAccessor
. А тепер директива NgModel називає спільну setUpControl
функцію:
export function setUpControl(control: FormControl, dir: NgControl): void {
if (!control) _throwError(dir, 'Cannot find control with');
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
control.validator = Validators.compose([control.validator !, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]);
dir.valueAccessor !.writeValue(control.value);
setUpViewChangePipeline(control, dir);
setUpModelChangePipeline(control, dir);
...
}
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void
{
dir.valueAccessor !.registerOnChange((newValue: any) => {
control._pendingValue = newValue;
control._pendingDirty = true;
if (control.updateOn === 'change') updateControl(control, dir);
});
}
function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
// control -> view
dir.valueAccessor !.writeValue(newValue);
// control -> ngModel
if (emitModelEvent) dir.viewToModelUpdate(newValue);
});
}
А ось міст у дії:
NgModel
встановлює контроль (1) та dir.valueAccessor !.registerOnChange
метод виклику . ControlValueAccessor
зберігає зворотний виклик у onChange
(2) властивості та запускає цей зворотний виклик, коли input
відбувається подія (3) . І нарешті updateControl
функція викликається всередині зворотного виклику (4)
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
де кутові виклики формують API control.setValue
.
Це коротка версія того, як це працює.
@Input() ngModel
і@Output() ngModelChange
для двосторонньої прив'язки, і я подумав, що це має бути досить містком. Це схоже на те, що робити те ж саме зовсім іншим способом. Можливо, я не повинен називати своє полеngModel
?