Les formulaires
Les formulaires basés sur un template
Un formulaire a pour objectif d’apporter une expérience agréable et pleine de cohésion lorsque l’utilisateur interagit avec les données de l’application.
Avec Angular, il est possible de créer des formulaires avec des contrôles utilisateurs qui vont utiliser le data binding (liaison de données). Ce data binding va permettre de synchroniser les données entre les composants et les vues. Une modification d’une propriété d’un composant sera automatiquement reflétée sur l’interface, et inversement une modification d’un contrôle HTML mettra à jour les propriétés du composant.
De plus, le développeur a les outils pour détecter ces changements d’état, et il sera d’autant plus facile de gérer la validation des entrées utilisateur et donc d’afficher les erreurs, ou non, en conséquence.
Tout développeur web a déjà eu l’occasion de développer un formulaire, que ce soit pour une page de login, un formulaire de contact ou un CRUD (Create, Read, Update, Delete) d’une entité d’un backend.
Dans ce chapitre, l’objectif est d’aborder les différentes problématiques lors du développement d’un composant-formulaire, en utilisant toute la puissance qu’apporte Angular pour ces problématiques.
Dans la suite du chapitre, le formulaire permettra l’édition d’une entité de type Product, avec les propriétés suivantes :
export class Product {
id: number;
name: string;
category: string;
}
Créer un composant formulaire
1. Le composant
Comme un composant classique, un composant formulaire contient deux parties. La première est la vue, qui est composée du template HTML et du style CSS, la deuxième, quant à elle, est le code lui-même. Ce code va gérer les données et les interactions de l’utilisateur.
La création du formulaire est donc identique à n’importe quel composant Angular. En ce qui concerne les imports, il est nécessaire d’importer le décorateur Component venant de @angular-core, puis la classe ou interface qui représente la donnée à manipuler dans le formulaire.
import { Component } from '@angular/core';
import { Product } from './product';
La déclaration du composant en lui-même est tout aussi basique, pour cet exemple, la vue HTML et le style CSS sont dans des fichiers séparés :
@Component({
selector: 'app-product-form',
templateUrl: './product-form.component.html',
styleUrls: ['./product-form.component.css']
})
Enfin, le contenu de la classe consiste en la récupération d’une instance de notre produit à modifier. Dans cet exemple, nous instancions un produit en dur, l’objectif sera, plus tard, de le fournir en paramètre du composant.
export class ProductFormComponent implements OnInit {
categories = ["Légumes", "Fruits", "Viandes"];
product: Product;
ngOnInit() {
this.product =
{
id: 0,
category: this.categories[0],
name: "Produit A"
}; ...
Les états et la validité d’un champ
1. Les états d’un input
Utiliser ngModel apporte, en plus du two-way data binding, la connaissance des états d’un input à un instant donné.
Par exemple, si un champ n’a jamais été visité, c’est-à-dire si l’utilisateur n’a jamais interagi avec celui-ci. Angular va ajouter au champ la classe ng-untouched. À l’inverse, si l’utilisateur a interagi avec le champ, ce sera la classe ng-touched qui sera appliquée.
Si le champ est valide, la classe ng-valid sera présente, si le champ est invalide, ce sera la classe ng-invalid.
La notion de validité est abordée plus tard dans le chapitre.
Enfin, Angular apporte également une gestion du "dirty", c’est-à-dire qu’en plus de la validité et la notion de "touched", l’application sait si le champ, "touched" ou "untouched", a été modifié. Lorsque le champ est modifié, la classe ng-dirty est appliquée, autrement c’est la classe ng-pristine qui l’est.
Voici un tableau qui récapitule ces informations :
Condition |
Classe si la condition est vraie |
Classe si la condition est fausse |
Le champ est valide |
ng-valid |
ng-invalid |
Le champ est modifié |
ng-dirty |
ng-pristine |
Il y a eu interaction avec le champ |
ng-touched |
ng-untouched |
Afin de bien comprendre ces différents états, et comment ils cohabitent, un exercice intéressant consiste à utiliser un template reference variable sur l’input, ainsi il sera possible de lister ces classes directement dans la vue. Sinon, il est parfaitement possible d’inspecter ces classes dans la console du navigateur, mais c’est une bonne occasion pour utiliser une template reference variable.
Le concept de template reference variable...
Soumettre le formulaire
L’objectif final pour l’utilisateur est de soumettre le formulaire une fois celui-ci rempli. Pour cela, il faut tout d’abord placer un bouton de type submit, chose déjà faite dans l’exemple précédent. Cependant, cela ne suffit pas, il faut expliquer au formulaire ce qu’il doit faire lorsqu’une validation est demandée. Pour cela, Angular met à disposition la directive ngSubmit.
La syntaxe de la directive est la suivante :
<form (ngSubmit)="onSubmit()">
Ainsi, la méthode onSubmit sera appelée lorsque le formulaire sera soumis.
Il faut donc ajouter cette méthode dans le composant :
onSubmit() {
console.log(`Le produit ${this.product.name} a été soumis,
il appartient à la catégorie ${this.product.category}`);
}
En réalité, Angular ajoute une directive ngForm automatiquement aux balises de type form. C’est cette directive qui va contenir les contrôles créés par les différents couples de directive ngModel et d’attribut name spécifiés sur les champs du formulaire.
La directive ngForm possédant elle-même la notion de validité au niveau du formulaire, il est alors possible de désactiver le bouton submit tant que le formulaire n’est pas valide.
<div class="product-container">
<h1>Mon Formulaire</h1>
<form (ngSubmit)="onSubmit()" #productForm="ngForm">
...
<button type="submit"
[disabled]="!productForm.form.valid">Submit</button>
</form>
</div>
Le formulaire possède un template reference variable qui pointe sur la directive ngForm de la balise...
Les formulaires et les FormControls
1. Les contrôles et les groupes de contrôles
Les exemples qui sont illustrés dans ce chapitre sont assez basiques et la validation est très sommaire. Cela convient souvent au besoin, mais il se peut que les attentes soient plus complexes et qu’il soit nécessaire de faire de la validation personnalisée, voire de la validation asynchrone.
Les formulaires Angular fonctionnent principalement avec deux types de composants : les FormControls (contrôles) et les FormGroups (groupes de contrôle).
Dans les exemples précédents, c’est uniquement ngModel qui était réellement utilisé dans les formulaires, puis Angular s’occupe du reste sans que le développeur s’en soucie. L’objectif pour la suite est de prendre la main sur ces entités de formulaire et de les manipuler nous-mêmes, ce qui permet d’aller plus loin.
Les contrôles sont des entités qui possèdent une valeur et un statut de validité, qui est déterminé par une fonction de validation qui, elle, est optionnelle. Un contrôle peut être lié à un champ, et à sa création, il prend trois paramètres, toutes optionnelles. Le premier paramètre est la valeur par défaut, le second est un validateur et le troisième est aussi un validateur, mais asynchrone.
Tout d’abord, pour utiliser cette fonctionnalité d’Angular il est nécessaire d’importer, au niveau du module, le ReactiveFormsModule de @angular/forms.
import { FormsModule, ReactiveFormsModule } from
'@angular/forms';
Si ReactiveFormsModule est importé, il n’est pas nécessaire d’importer FormsModule, ReactiveFormsModule importe lui-même FormsModule.
Pour utiliser un contrôle, il faut donc l’instancier...