Angular forms et Self-management

Nicolas Frizzarin
Mar 18 · 6 min read

Ce n'est plus un secret pour personne, le framework Angular propose deux manières de créer un formulaire:
- le template-driven où le formulaire est géré par le template
- le model-driven où le formulaire est géré par le controller (fichier component.ts)

Lorsque l'on choisit de créer un formulaire à l'aide du model-driven, Angular nous propose différentes classes à instancier telles que FormGroup, FormControl ou encore FormArray mais aussi un service à injecter nommé FormBuilder.

Avec ces différents éléments, le formulaire est forcément créé dans le composant et toutes les souscriptions sur les changements de valeurs aussi. Ce qui fait que notre composant peut vite devenir lourd d'autant plus qu'il devra gérer également les actions de notre vue.

L'idée est donc de déléguer la gestion du formulaire au …. formulaire lui même.
Ce qui nous amène donc à créer un formulaire qui s'auto-manage!!!

Qu'est ce qu'un formulaire qui s'auto-manage ?

Concrètement ce n'est pas au composant de gérer les règles de validation (ajouter/supprimer la requisition d'un champ en fonction d'un autre) mais au formulaire lui même.
Le composant doit uniquement initialiser le formulaire.

Comment créer un formulaire qui s'auto-manage ?

Pour ce faire, nous allons réaliser un formulaire représentant l'inscription d'un nouveau restaurateur dans un système de gestion.
Un restaurateur se matérialise par les champs suivants :
- lastname: type: string, validations: [requis]
- firstname: type: string, validations: [requis],
- age: type: number, validations: [aucune],
- birthDate: type: number, validations: [ requis si age est renseigné]
- addressRestaurants: type Array<FormGroup> → { address: string; city: string; zipCode: number; }

Un formulaire auto-managé est un formulaire qui s'appuie obligatoirement sur la gestion des formulaires de type model-driven.

Voyons comment nous écririons ce formulaire de manière classique.

L'objectif est de sortir toute cette logique afin que le composant soit plus léger sans perdre toute la puissance que nous offre Angular.

Que sont FormGroup, FormControl et FormArray ?

Modélisation

En réalisant notre formulaire de cette façon, une infinité de solutions s'offre à nous, notamment la possibilité d'overrider les méthodes et les propriétés que nous offre la classe FormGrouppour réaliser du typage fort ou encore pour réaliser notre propre logique de patchValuepar exemple.

En effet par défaut la propriété value a pour typage any et la propriété controls a pour typage { [key: string]: AbstractControl }

En réalisant notre formulaire de cette manière nous gagnons en typage, ce qui permet à nos IDEs de nous faire de l'auto-complétion intelligente.

Attention: il est également possible de le faire avec des formulaires créés dans le composant en utilisant la fonctionnalité de cast ce qui n'est pas très propre à mon goût.

Mais nous pouvons encore mieux faire !!!

Regardons de plus près le contrôle addressRestaurants. Le contrôle addressRestaurants est un FormArray dont chaque contrôle est un FormGroup.

Commençons par matérialiser notre FormGroupaddresRestaurant.

Dans ce cas précis je passe des datas au constructor permettant de setter immédiatement le contrôle, bien évidemment ces datas peuvent être optionnelles.

Matérialisons notre FormArray

En réalisant la modélisation de cette manière nous gagnons une nouvelle fois en typage et cela permet d'éviter l' avertissement de typage suivant dans votre IDE (à condition que vous compilez avec l'option strictTemplates → ce qui est une bonne pratique puisque ce mode sera celui par défaut dans la version 12 d'Angular):
Type AbstractControl is not assignable to type FormGroup

Et oui, par défaut les contrôles des FormArray sont de type AbstractControl ce qui n'est pas totalement notre cas ici car chaque contrôle de notre tableau est en réalité un FormGroup et plus précisément un adressRestaurantForm

Voici ce que nous donne la modélisation dans sa globalité

Comment update un tel formulaire si nous étions dans un mode de modification et non de création ?

Et celle qui nous intéresse le plus ici, vous l'avez deviné est … patchValue.

La méthode patchValuepermet de modifier notre fomulaire en matchant 'au plus proche de nos contrôles'.

Modifions notre code afin de pouvoir initialiser un formulaire prérempli

Ici je construis un formulaire en overridant la méthode initiale patchValue pour la faire correspondre à notre besoin.

Je 'set' les valeurs simples en utilisant la méthode super.patchValueet pour le tableau d'adresses je réalise une nouvelle fonction patchValue correspondant à mes besoins

Ici j'utilise deux méthodes que nous offre la classe FormArray:
- clear qui permet de réinitialiser les contôles d'un tableau
- push qui permet d'ajouter un contrôle dans un tableau

Où enregistrer nos subscriptions ?

Quand on souscrit à un observable, obligatoirement on doit se poser la question de comment unsubscribe.
Notre formulaire n'étant pas déclaré dans un composant, où seulement sous forme d'instance, la grande question à se poser est donc comment unsubscribe.

Et bien par deux méthodes:

  • Dans le composant, lors de l'instance il suffit de le lui passer un second paramètre whenUnsubscribe$ de type Subject . Puis faire un complete de ce subject dans le hook ngOnDestroydu composant
  • Dans le composant supprimer l'instance de votre formulaire dans le hook ngOnDestroy du composant. Ainsi la susbscription devient null

Avantages / Inconvénients

  • un typage strict: le fait d'ajouter un typage strict à vos formulaires rend plus robuste votre code et permet d'éviter un bon nombre d'erreurs. Cela permet également d'avoir une meilleur auto complétion dans vos IDEs
  • alléger votre composant: créer un formulaire est aussi simple que d'instancier une classe.
  • logique découplée: la logique de votre formulaire est faite dans la modélisation du formulaire lui-même et non dans le composant
  • réutilisation: la réutilisation de vote formulaire est plus simple puisqu'une simple instance permet de le créer mais aussi de souscrire aux différentes logiques. Si vous aimez créer des libraires, cette technique vous permet d'exporter facilement vos formulaires
  • tests: vos tests sont faciles à créer pusqu'il ne s'agit de tester qu'une simple classe

Malgré ces avantages, cette méthode possède un inconvénient si l'on peut appeler ça un inconvénient à proprement parler.
Imaginons que votre classe possèdent des validateurs asynchrones qui utilisent un service; si vous souhaitez utiliser ce service il vous faudra le passer dans le constructeur qui instancie votre formulaire.

CodeShake

Learnings and insights from SFEIR community.

CodeShake

Learnings and insights from SFEIR community.

Nicolas Frizzarin

Written by

CodeShake

Learnings and insights from SFEIR community.