web-dev-qa-db-fra.com

Qu'est-ce que ngDefaultControl in Angular?

Non, ce n'est pas une question en double. Vous voyez, il y a une tonne de questions et de problèmes dans SO et Github qui prescrivent que j'ajoute cette directive à une balise comportant la directive [(ngModel)] et ne figurant pas dans un formulaire. Si je ne l'ajoute pas, j'obtiens une erreur:

ERROR Error: No value accessor for form control with unspecified name attribute

Ok, l'erreur disparaît si je mets cet attribut là. Mais attendez! Personne ne sait ce que ça fait! Et le médecin d'Angular n'en parle pas du tout. Pourquoi ai-je besoin d'un accesseur de valeur quand je sais que je n'en ai pas besoin? Comment cet attribut est-il connecté aux accesseurs de valeur? Que fait cette directive? Qu'est-ce qu'un acesseur de valeur et comment l'utiliser?

Et pourquoi tout le monde continue-t-il à faire des choses qu’ils ne comprennent pas? Ajoutez simplement cette ligne de code et ça marche, merci, ce n’est pas la bonne façon d’écrire de bons programmes.

Puis. Je lis non pas un mais deux énormes guides sur les formes en Angular et une section sur ngModel:

Et tu sais quoi? Pas une seule mention d'accesseurs de valeur ou de ngDefaultControl. Où est-ce?

70
Gherman

[ngDefaultControl]

Les commandes tierces nécessitent un ControlValueAccessor pour fonctionner avec les formes angular. Beaucoup d'entre eux, comme le <paper-input> de Polymer, se comportent comme l'élément natif <input> et peuvent donc utiliser le DefaultValueAccessor. L'ajout d'un attribut 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 attrubute a été introduit.

Il s’appelait ng-default-control attribut dans les versions alpha de angular2 .

Donc, ngDefaultControl est 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 cela signifie?

Cela signifie que nous pouvons appliquer cet attribut à un élément (comme le composant polymer qui n'a pas son propre accesseur de valeur). Donc, cet élément prendra le comportement de DefaultValueAccessor et nous pouvons utiliser cet élément avec les formes angular.

Sinon, vous devez fournir votre propre implémentation de ControlValueAccessor

ControlValueAccessor

Angular états de la documentation

Un ControlValueAccessor agit comme un pont entre l'API Angular forms et un élément natif du DOM.

Écrivons le modèle suivant dans une application angular2 simple:

<input type="text" [(ngModel)]="userName">

Pour comprendre le comportement de notre input ci-dessus, nous devons savoir quelles directives sont appliquées à cet élément. Ici angular donne un indice avec l'erreur:

Rejet de la promesse non gérée: erreurs d'analyse du modèle: impossible de se lier à 'ngModel' car il ne s'agit pas d'une propriété connue de 'entrée'.

OK, nous pouvons ouvrir SO et obtenir la réponse suivante: importer FormsModule sur 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 {}

enter image description here

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 {

La directive NgControlStatus manipule simplement des classes telles que ng-valid, ng-touched, ng-dirty et nous pouvons l'omettre ici.


DefaultValueAccesstor fournit le jeton NG_VALUE_ACCESSOR dans le tableau de 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 {

La directive NgModel injecte dans le jeton du constructeur NG_VALUE_ACCESSOR qui a été déclaré sur le même élément Host.

export NgModel extends NgControl implements OnChanges, OnDestroy {
 constructor(...
  @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {

Dans notre cas, NgModel injectera DefaultValueAccessor. Et maintenant, la directive NgModel appelle shared setUpControl function:

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:

enter image description here

NgModel configure le contrôle (1) et appelle la méthode dir.valueAccessor !.registerOnChange. ControlValueAccessor enregistre le rappel dans la propriété onChange (2) et déclenche ce rappel lorsque l'événement input se produit (3) . Et enfin, la fonction updateControl est appelée à l'intérieur du rappel (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 de formulaire control.setValue.

C'est une version courte de la façon dont cela fonctionne.

120
yurzui