Ответ 1
[ngDefaultControl]
Элементы сторонних разработчиков требуют, чтобы ControlValueAccessor
функционировал с формами angular. Многие из них, такие как Polymer <paper-input>
, ведут себя как родной элемент <input>
и поэтому могут использовать DefaultValueAccessor
. Добавление атрибута ngDefaultControl
позволит им использовать эту директиву.
<paper-input ngDefaultControl [(ngModel)]="value>
или
<paper-input ngDefaultControl formControlName="name">
Итак, это основная причина, по которой этот attrubute был введен.
Он был назван атрибутом 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
, и мы можем использовать этот элемент с формами angular.
В противном случае вы должны предоставить свою собственную реализацию ControlValueAccessor
ControlValueAccessor
Angular состояния документов
A ControlValueAccessor действует как мост между API форм angularи собственный элемент в DOM.
Запишите следующий шаблон в простом приложении angular2:
<input type="text" [(ngModel)]="userName">
Чтобы понять, как будет выглядеть наш input
выше, нам нужно знать, какие директивы применяются к этому элементу. Здесь angular выдает некоторый намек на ошибку:
Необработанное отклонение обещаний: ошибки анализа шаблона: не удается привязать к 'ngModel', поскольку он не является известным свойством 'input'.
Хорошо, мы можем открыть SO и получить ответ: import 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});
}
где angular вызывает формы API control.setValue
.
Это короткая версия того, как она работает.