[ngDefaultControl]
Les contrôles tiers nécessitent un ControlValueAccessor
pour fonctionner avec des formes angulaires. Beaucoup d'entre eux, comme Polymer <paper-input>
, se comportent comme l' <input>
élément natif et peuvent donc utiliser le DefaultValueAccessor
. L'ajout d'un ngDefaultControl
attribut leur permettra d'utiliser cette directive.
<paper-input ngDefaultControl [(ngModel)]="value>
ou
<paper-input ngDefaultControl formControlName="name">
C'est donc la principale raison pour laquelle cet attrubute a été introduit.
Il a été appelé ng-default-control
attribut dans les versions alpha d'angular2 .
Il en ngDefaultControl
va de même pour l'un des sélecteurs de la directive 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 {
Qu'est-ce que ça veut dire?
Cela signifie que nous pouvons appliquer cet attribut à un élément (comme un composant polymère) qui n'a pas son propre accesseur de valeur. Donc, cet élément prendra le comportement deDefaultValueAccessor
et nous pouvons utiliser cet élément avec des formes angulaires.
Sinon, vous devez fournir votre propre implémentation de ControlValueAccessor
ControlValueAccessor
États de la documentation angulaire
Un ControlValueAccessor agit comme un pont entre l'API Angular Forms et un élément natif dans le DOM.
Écrivons le modèle suivant dans une application angular2 simple:
<input type="text" [(ngModel)]="userName">
Pour comprendre comment input
se comportera ce qui précède, nous devons savoir quelles directives sont appliquées à cet élément. Ici, angular donne un indice avec l'erreur:
Rejet de promesses non gérées: erreurs d'analyse de modèle: impossible de se lier à «ngModel» car ce n'est pas une propriété connue de «input».
D'accord, nous pouvons ouvrir SO et obtenir la réponse: importez FormsModule
dans 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 recherches, nous pouvons découvrir que trois directives seront appliquées à notre 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
directive seulement manipule des classes comme ng-valid
, ng-touched
, ng-dirty
et nous pouvons l' omettre ici.
DefaultValueAccesstor
fournit un NG_VALUE_ACCESSOR
jeton 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
directive injecte dans le NG_VALUE_ACCESSOR
jeton de constructeur 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
va injecter DefaultValueAccessor
. Et maintenant, la directive NgModel appelle la setUpControl
fonction partagée :
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
configure le contrôle (1) et appelle la dir.valueAccessor !.registerOnChange
méthode. ControlValueAccessor
stocke le rappel dans la propriété onChange
(2) et déclenche ce rappel lorsque l' input
événement se produit (3) . Et enfin la updateControl
fonction 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ù les appels angulaires forment l'API control.setValue
.
C'est une version courte de la façon dont cela fonctionne.
@Input() ngModel
et@Output() ngModelChange
pour la liaison bidirectionnelle et j'ai pensé que cela devrait suffire à un pont. Cela ressemble à faire la même chose d'une manière complètement différente. Peut-être que je ne devrais pas nommer mon champngModel
?@Input() value; @Output() valueChange: EventEmitter<any> = new EventEmitter();
, puis utiliser simplement[(value)]="someProp"
ngModel
et Angular a commencé à me lancer une erreur et à demander avec ControlValueAccessor.