Angular forms et Self-management

Nicolas Frizzarin
CodeShake
Published in
6 min readMar 18, 2021

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 ?

Un formulaire qui s'auto-manage est un formulaire qui auto gère ses subscriptions et la logique à exécuter lors des différents changements de valeurs.

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 ?

Nous allons créer un formulaire étape par étape permettant de comprendre la logique de création de ce genre de formulaires mais aussi d'en voir la puissance, les avantages mais aussi le peu d'inconvénients qu'ils possèdent.

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 ?

Ces trois composantes sont de simples classes, et par définition une classe est étendable :).

Modélisation

Commençons par créer une classe RestorerFormqui étend la classe FormGroup

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 ?

En écrivant nos formulaires de cette manière, nous avons accès à toutes les méthodes que nous offrent les classes que l'on a étendues

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 ?

La logique de susbscription doit automatiquement s'exécuter lorsque nous créons notre formulaire, c'est pour cette raison qu'elle s'effectue dans le constructeur de la manière suivante:

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

Cette technique apporte plusieurs avantages

  • 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.

--

--