[ngDefaultControl]
Kontrolki innych firm wymagają, ControlValueAccessor
aby działały z formami kątowymi. Wiele z nich, podobnie jak Polymer <paper-input>
, zachowuje się jak <input>
element natywny i dlatego może używać rozszerzenia DefaultValueAccessor
. Dodanie ngDefaultControl
atrybutu umożliwi im korzystanie z tej dyrektywy.
<paper-input ngDefaultControl [(ngModel)]="value>
lub
<paper-input ngDefaultControl formControlName="name">
To jest główny powód, dla którego wprowadzono ten atrubut.
Nazywało się to ng-default-control
atrybutem w wersjach alfa angular2 .
Tak ngDefaultControl
jest jednym z selektorów dyrektywy 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 {
Co to znaczy?
Oznacza to, że możemy zastosować ten atrybut do elementu (takiego jak komponent polimerowy), który nie ma własnego akcesorium wartości. Więc ten element będzie się zachowywał DefaultValueAccessor
i możemy go używać z formami kątowymi.
W przeciwnym razie musisz zapewnić własną implementację ControlValueAccessor
ControlValueAccessor
Angular Docs stwierdza
ControlValueAccessor działa jako pomost między interfejsem API Angular formularzy a natywnym elementem w DOM.
Napiszmy następujący szablon w prostej aplikacji angular2:
<input type="text" [(ngModel)]="userName">
Aby zrozumieć, jak input
zachowa się powyższe, musimy wiedzieć, które dyrektywy są zastosowane do tego elementu. Tutaj angular daje wskazówkę z błędem:
Nieobsłużone odrzucenie obietnicy: Błędy analizy szablonu: nie można powiązać z „ngModel”, ponieważ nie jest to znana właściwość „input”.
Okay, możemy otworzyć SO i uzyskać odpowiedź: zaimportuj FormsModule
do @NgModule
:
@NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
Sprowadziliśmy to i wszystko działa zgodnie z przeznaczeniem. Ale co się dzieje pod maską?
FormsModule eksportuje dla nas następujące dyrektywy:
@NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
Po pewnym zbadaniu możemy odkryć, że trzy dyrektywy zostaną zastosowane do naszego 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
Dyrektywa tylko manipuluje klas podoba ng-valid
, ng-touched
, ng-dirty
a możemy go pominąć tutaj.
DefaultValueAccesstor
udostępnia NG_VALUE_ACCESSOR
token w tablicy dostawców:
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
dyrektywa wprowadza do NG_VALUE_ACCESSOR
tokenu konstruktora, który został zadeklarowany w tym samym elemencie hosta.
export NgModel extends NgControl implements OnChanges, OnDestroy {
constructor(...
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
W naszym przypadku NgModel
wstrzyknie DefaultValueAccessor
. A teraz dyrektywa NgModel wywołuje współdzieloną setUpControl
funkcję:
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) => {
dir.valueAccessor !.writeValue(newValue);
if (emitModelEvent) dir.viewToModelUpdate(newValue);
});
}
A oto most w akcji:
NgModel
ustawia control (1) i wywołuje dir.valueAccessor !.registerOnChange
metodę. ControlValueAccessor
przechowuje wywołanie zwrotne we właściwości onChange
(2) i uruchamia to wywołanie zwrotne, gdy input
nastąpi zdarzenie (3) . I wreszcie updateControl
funkcja jest wywoływana wewnątrz callback (4)
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
gdzie wywołania kątowe tworzą API control.setValue
.
To krótka wersja tego, jak to działa.