[ngDefaultControl]
Les contrôles effectués par des tiers nécessitent un ControlValueAccessor
pour fonctionner avec des formes angulaires. Beaucoup d'entre elles, comme celle de Polymer <paper-input>
se comportent comme les <input>
et peut donc utiliser l'élément DefaultValueAccessor
. Ajout d'un ngDefaultControl
leur permettra d'utiliser cette directive.
<paper-input ngDefaultControl [(ngModel)]="value>
ou
<paper-input ngDefaultControl formControlName="name">
C'est donc la raison principale pour laquelle cet attribut a été introduit.
Il s'appelait ng-default-control
attribut dans les versions alpha d'angular2 .
Alors ngDefaultControl
est l'un des sélecteurs pour DefaultValueAccessor directive :
@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 {
Qu'est-ce que cela signifie ?
Cela signifie que nous pouvons appliquer cet attribut à un élément (comme un composant polymère) qui ne possède pas son propre accesseur de valeur. Ainsi, cet élément prendra le comportement de DefaultValueAccessor
et nous pouvons utiliser cet élément avec les formulaires angulaires.
Sinon, vous devez fournir votre propre implémentation de ControlValueAccessor
ControlValueAccessor
Angulaire états docs
Un ControlValueAccessor agit comme un pont entre l'API des formulaires Angular et un élément natif dans le DOM.
Écrivons le modèle suivant dans une simple application angular2 :
<input type="text" [(ngModel)]="userName">
Pour comprendre comment notre input
ci-dessus se comportera, nous devons savoir quelles directives sont appliquées à cet élément. Ici, angular donne un indice avec l'erreur :
Rejet d'une promesse non gérée : Erreurs d'analyse du modèle : Impossible de se lier à ngModel' car ce n'est pas une propriété connue de 'input'.
Ok, nous pouvons ouvrir SO et obtenir la réponse : import FormsModule
à votre @NgModule
:
@NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
Nous l'avons importé et tout fonctionne comme prévu. Mais que se passe-t-il sous le capot ?
FormsModule exporte pour nous les directives suivantes :
@NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
Après quelques investigations, nous pouvons découvrir que trois directives seront appliquées à notre input
-
NgControlStatus
@Directive({ selector : '[formControlName],[ngModel],[formControl]', ... }) export class NgControlStatus extends AbstractControlStatus { ... }
-
NgModel
@Directive({ selector : '[ngModel]:not([formControlName]):not([formControl])', providers : [formControlBinding], exportAs : 'ngModel'. }) export class NgModel extends NgControl implements OnChanges,
-
ACCESSEUR DE VALEUR PAR DÉFAUT
@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
ne fait que manipuler des classes comme ng-valid
, ng-touched
, ng-dirty
et nous pouvons l'omettre ici.
DefaultValueAccesstor
fournit NG_VALUE_ACCESSOR
dans le tableau des fournisseurs :
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
injecte dans le constructeur NG_VALUE_ACCESSOR
qui a été déclaré sur le même élément hôte.
export NgModel extends NgControl implements OnChanges, OnDestroy {
constructor(...
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
Dans notre cas NgModel
injectera DefaultValueAccessor
. Et maintenant les appels de la directive NgModel partagée setUpControl
fonction :
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);
});
}
Et voici le pont en action :
NgModel
met en place un contrôle (1) et appelle dir.valueAccessor !.registerOnChange
méthode. ControlValueAccessor
enregistre le rappel dans onChange
(2) et déclenche ce rappel lorsque input
l'événement se produit (3) . Et enfin updateControl
est appelée à l'intérieur du callback (4)
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
où angular appelle l'API des formulaires control.setValue
.
C'est une version courte de la façon dont cela fonctionne.
5 votes
> "Et pourquoi tout le monde continue-t-il à faire des choses qu'ils ne comprennent pas du tout?" - Oui! exactement! cela pourrait utiliser quelques points d'exclamation de plus, cependant ;-)