[ngDefaultControl]
Steuerungen von Drittanbietern erfordern eine ControlValueAccessor
Funktion mit eckigen Formen. Viele von ihnen, wie Polymers <paper-input>
, verhalten sich wie das <input>
native Element und können daher das verwenden DefaultValueAccessor
. Durch Hinzufügen eines ngDefaultControl
Attributs können sie diese Anweisung verwenden.
<paper-input ngDefaultControl [(ngModel)]="value>
oder
<paper-input ngDefaultControl formControlName="name">
Dies ist der Hauptgrund, warum dieser Attribut eingeführt wurde.
In Alpha-Versionen von angle2 wurde es ng-default-control
Attribut genannt .
So ngDefaultControl
ist einer der Selektoren für die DefaultValueAccessor- Direktive:
@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 {
Was heißt das?
Dies bedeutet, dass wir dieses Attribut auf Elemente (wie Polymerkomponenten) anwenden können, die keinen eigenen Wertzugriff haben. Dieses Element übernimmt also das Verhalten von DefaultValueAccessor
und wir können dieses Element mit eckigen Formen verwenden.
Andernfalls müssen Sie Ihre eigene Implementierung von bereitstellen ControlValueAccessor
ControlValueAccessor
Angular Docs Zustände
Ein ControlValueAccessor fungiert als Brücke zwischen der Angular Forms-API und einem nativen Element im DOM.
Schreiben wir die folgende Vorlage in einer einfachen Angular2-Anwendung:
<input type="text" [(ngModel)]="userName">
Um zu verstehen, wie sich unser input
oben genanntes Verhalten verhält, müssen wir wissen, welche Anweisungen auf dieses Element angewendet werden. Hier gibt Winkel einen Hinweis mit dem Fehler:
Nicht behandelte Ablehnung von Versprechungen: Fehler beim Analysieren von Vorlagen: Kann nicht an 'ngModel' gebunden werden, da dies keine bekannte Eigenschaft von 'input' ist.
Okay, wir können SO öffnen und die Antwort erhalten: Importieren FormsModule
in Ihre @NgModule
:
@NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
Wir haben es importiert und alles funktioniert wie vorgesehen. Aber was ist unter der Haube los?
FormsModule exportiert für uns folgende Richtlinien:
@NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
Nach einigen Untersuchungen können wir feststellen, dass drei Richtlinien auf unsere angewendet werden 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
Richtlinie nur manipuliert Klassen mögen ng-valid
, ng-touched
, ng-dirty
und wir können es hier weglassen.
DefaultValueAccesstor
stellt NG_VALUE_ACCESSOR
Token im Provider-Array bereit :
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
Die Direktive fügt ein Konstruktortoken ein NG_VALUE_ACCESSOR
, das auf demselben Hostelement deklariert wurde.
export NgModel extends NgControl implements OnChanges, OnDestroy {
constructor(...
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
In unserem Fall NgModel
wird injizieren DefaultValueAccessor
. Und jetzt ruft die NgModel-Direktive die gemeinsame setUpControl
Funktion auf:
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);
});
}
Und hier ist die Brücke in Aktion:
NgModel
richtet die Steuerung (1) ein und ruft die dir.valueAccessor !.registerOnChange
Methode auf. ControlValueAccessor
speichert den Rückruf in der Eigenschaft onChange
(2) und löst diesen Rückruf aus, wenn ein input
Ereignis eintritt (3) . Und schließlich wird die updateControl
Funktion innerhalb des Rückrufs aufgerufen (4).
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
Wobei Winkelaufrufe API bilden control.setValue
.
Das ist eine kurze Version davon, wie es funktioniert.
@Input() ngModel
und@Output() ngModelChange
für bidirektionale Bindung gemacht und ich dachte, es sollte genug eine Brücke sein. Das sieht so aus, als würde man dasselbe auf eine ganz andere Art und Weise tun. Vielleicht sollte ich mein Feld nicht benennenngModel
?@Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();
und dann einfach[(value)]="someProp"
ngModel
und Angular fing an, einen Fehler auf mich zu werfen und mit ControlValueAccessor danach zu fragen.