Commandez vos pizzas grâce aux ControlValueAccessors, dans votre application Angular

Les formulaires se trouvent dans la plupart des applications web. Utilisés pour recueillir vos données (pour les revendre ensuite?), ils sont un des points d’entrée des informations dans un système informatique.

Le framework Angular permet d’ailleurs de gérer les formulaires de deux manières

Cet article abordera la manière dont Angular vous permet d’intégrer vos composants au sein de ces deux approches, grâce à l’interface ControlValueAccessor d’Angular.

Pizza Hat

Chez Pizza Hat on en est convaincu, une révolution est à venir dans le monde de la pizza. Au pôle marketing d’ailleurs on croit avoir trouvé une ‘key-feature’ qui pourrait mener à bien cette révolution...

Cette fonctionnalité en question, pensée pour être raccord avec le nom de l’enseigne, se trouve simple et originale. Plutôt qu’une liste déroulante, ennuyante au possible, avec les mots “petite”, “moyenne” et “large” pour sélectionner la taille de la pizza, les utilisateurs de l’application cliqueront sur des chapeaux !

Comme ce choix de design sera sûrement amené à être utilisé dans plusieurs formulaires au sein de l’application, le développeur en charge décide d’en faire un composant Angular pour la réutilisabilité : bonne idée !

Sans ControlValueAccessor

Pour l’utiliser dans son formulaire, il décide dans une première ébauche d’utiliser ce qu’il a appris dans l’interaction entre les composants.

Grâce aux notions d’input et d’output d’un composant, il arrive à produire le résultat voulu : le composant parent donne la valeur au composant fils et quand l’utilisateur change la valeur du composant fils, celui-ci envoie un événement pour notifier le parent de se mettre à jour.

En regardant le résultat, le développeur n’est pas très convaincu. En effet, il doit écrire un code spécifique pour associer la valeur de son formulaire à son composant alors qu’au final son composant est juste un input, pas standardisé par le W3C d’accord, mais un input quand même.

En approfondissant un peu la doc, il se rend compte qu’Angular a créé une interface pour adresser ce genre de problème : ControlValueAccessor ou CVA pour les intimes.

Avec ControlValueAccessor

L’interface ControlValueAccessor va servir de pont entre votre composant et l’API de gestion de formulaire d’Angular. Votre composant devra implémenter 3 fonctions :

  • writeValue(obj: any): void

Le framework appelle cette fonction quand une nouvelle valeur (qui est passée en paramètre) est affectée à votre composant. A vous de changer l'état de votre composant pour refléter cette nouvelle valeur

  • registerOnChange(fn: any): void

Le framework appelle cette fonction lors de l’initialisation de votre composant, le paramètre envoyé est une fonction de callback que vous devrez appeler, quand votre composant change de valeur (suite à un input utilisateur)

  • registerOnTouched(fn: any): void

Le framework appelle cette fonction lors de l’initialisation de votre composant, le paramètre envoyé est une fonction de callback que vous devrez appeler quand votre composant a été touché par l’utilisateur. Cela servira à Angular pour gérer le flag “touched” du FormControl

Après réécriture voici à quoi ressemble maintenant notre composant :

Plusieurs changements au niveau du composant :

providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: PizzaSizeSelectionComponent,
    }
  ]

On est obligé de rajouter ce bout de code afin de dire au framework que notre composant implémente bien l’interface, et donc lorsqu’il le croisera pendant le rendu de la page, il pourra appeler les méthodes que nous avons définies.

@HostListener('click') hostTouched() {
    if (this.onTouched) {
      this.onTouched();
    }
  }

Nous avons défini une fonction hostTouched, qui sera appelée à chaque clic sur notre composant (grâce au décorateur HostListener) et si nous avons réussi à capturer la fonction passée en paramètre de registerOnTouched, alors nous l’appelons.

Pour le reste nous utilisons un BehaviorSubject afin de gérer le changement de valeur au sein de notre composant. Lorsque Angular nous pousse une valeur, nous l’ajoutons au BehaviorSubject grâce à la méthode next et nous faisons de même lorsque l’utilisateur clique sur une des vignettes avec le code suivant dans le HTML (click)="currentValue.next('medium')"

A présent notre composant peut être utilisé comme n’importe quel input, notre HTML niveau formulaire devient donc :

<app-pizza-size-selection formControlName="pizzaSize"></app-pizza-size-selection>

A noter que le formulaire présenté ici utilise l’approche “reactive form” mais nous pourrions très bien utiliser l’approche “template driven” en utilisant le fameuse directive ngModel sur notre composant

<app-pizza-size-selection [(ngModel)]="pizzaSize"></app-pizza-size-selection>

Le MVP est prêt à partir en prod, plus qu'à trouver un pizzaïolo !