Vous savez déjà créer des FormGroups simples, mais on a dit que les formulaires complexes nécessitaient d'autres approches.
Dans ce chapitre, vous allez découvrir et implémenter plusieurs techniques :
vous allez créer des FormControls indépendants afin de pouvoir les manipuler plus facilement ;
vous allez créer des FormGroups à partir de FormControls indépendants pour les regrouper et pouvoir les valider ensemble ;
vous réunirez enfin le tout dans un seul FormGroup final afin de pouvoir récupérer toutes les valeurs en même temps, et afin de réagir aux événements de validation : ce FormGroup héritera des validations de tous ses enfants.
Commençons par le plus simple, et allons-y crescendo !
Créez un FormGroup et des FormControls
Les premiers éléments que vous allez implémenter sont un FormGroup pour la partie "Informations personnelles", et des FormControls indépendants pour "Comment préférez-vous être contacté(e) ?" et "Téléphone".
Commencez par créer les variables et une méthode qui les initialisera :
export class ComplexFormComponent implements OnInit {
mainForm!: FormGroup;
personalInfoForm!: FormGroup;
contactPreferenceCtrl!: FormControl;
phoneCtrl!: FormControl;
constructor(private formBuilder: FormBuilder) { }
ngOnInit(): void {
this.initFormControls();
this.initMainForm();
}
initMainForm(): void {
}
Vous pouvez en profiter pour passer les deux méthodes appelées dans ngOnInit()
en private
:
ngOnInit(): void {
this.initFormControls();
this.initMainForm();
}
private initMainForm(): void {
//...
}
private initFormControls(): void {
//...
}
Pourquoi faire ça ? Quelle est la différence entre une méthode public
et une méthode private
dans un component ?
La différence principale est qu'une méthode private
ne peut pas être appelée depuis le template.
Ça évite d'avoir de l'autocomplétion dans le template, par exemple, pour une méthode qui ne doit pas y être appelée. Ça explique aussi à vos coéquipiers (ou à vous-même six mois plus tard !) que cette méthode est prévue pour être appelée à l'intérieur du component.
Il est temps d'initialiser le FormGroup et les FormControls :
private initFormControls(): void {
this.personalInfoForm = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
});
this.contactPreferenceCtrl = this.formBuilder.control('email');
this.phoneCtrl = this.formBuilder.control('');
}
Ici :
personalInfoForm
est un FormGroup tout à fait banal avec deux contrôles requis ;phoneCtrl
est un FormControl indépendant sans valeur par défaut ;contactPreferenceCtrl
est un FormControl indépendant avec une valeur par défaut :le contrôle dans le template sera un bouton radio, donc
email
sera sélectionné par défaut,il n'est pas nécessaire d'ajouter un
Validators.required
– l'utilisateur ne pourra pas désélectionner le contrôle.
Voilà, les contrôles basiques sont prêts ! Montons d'un cran et créons des FormGroups à partir de FormControls indépendants.
Créez des FormGroups avec des FormControls
Il reste deux "groupes" de contrôles à générer : la partie email
et la partie loginInfo
.
Pour la partie email
, vous allez créer deux FormControls indépendants et un FormGroup :
emailCtrl!: FormControl;
confirmEmailCtrl!: FormControl;
emailForm!: FormGroup;
Vous allez ensuite initialiser les FormControls avant de générer le FormGroup à partir de ces FormControls :
this.emailCtrl = this.formBuilder.control('');
this.confirmEmailCtrl = this.formBuilder.control('');
this.emailForm = this.formBuilder.group({
email: this.emailCtrl,
confirm: this.confirmEmailCtrl
});
Pourquoi cette approche ?
Parce que ça permet d'avoir la main facilement sur les contrôles (vous en aurez besoin pour gérer la validation, par exemple) et de traiter les deux contrôles ensemble – pour récupérer leurs valeurs et pour les valider ensemble, par exemple.
Pour loginInfo
, vous allez faire quelque chose de similaire, mais qui regroupe toutes les approches que vous avez vues jusqu'ici !
passwordCtrl!: FormControl;
confirmPasswordCtrl!: FormControl;
loginInfoForm!: FormGroup;
// ...
this.passwordCtrl = this.formBuilder.control('', Validators.required);
this.confirmPasswordCtrl = this.formBuilder.control('', Validators.required);
this.loginInfoForm = this.formBuilder.group({
username: ['', Validators.required],
password: this.passwordCtrl,
confirmPassword: this.confirmPasswordCtrl
});
Vous aurez besoin de pouvoir facilement manipuler passwordCtrl
et confirmPasswordCtrl
, mais pas username
. Vous pouvez donc mélanger les approches, avec username
comme contrôle simple et les contrôles de mot de passe comme des FormControls indépendants !
Vous avez également ajouté de la validation aux deux contrôles de mot de passe, en passant un Validator
comme deuxième argument à FormBuilder.control
.
Toutes les pièces sont prêtes : il est temps de les réunir !
Construisez le FormGroup final
Pour récupérer, en fin de compte, un objet de la forme ComplexFormValue, l'initialisation de mainForm
se passe ainsi :
private initMainForm(): void {
this.mainForm = this.formBuilder.group({
personalInfo: this.personalInfoForm,
contactPreference: this.contactPreferenceCtrl,
email: this.emailForm,
phone: this.phoneCtrl,
loginInfo: this.loginInfoForm
});
}
Jusqu'ici, tout ce que vous avez passé à FormBuilder.group
, c'étaient des FormControls. Ici, vous passez également des FormGroups !
Avec ce montage, vous profitez pleinement des avantages des FormGroups et de ceux des FormControls :
vous pouvez traiter des champs ensemble, pour la validation par exemple ;
vous pouvez récupérer leurs valeurs sous forme d'objet unique ;
vous manipulez facilement les contrôles auxquels vous avez besoin, par un accès rapide.
Il ne reste plus qu'à créer le template en reliant les éléments HTML à leurs FormControls respectifs, et le montage créé ci-dessus vous offre plusieurs options.
Reliez le template
Commencez par le premier FormGroup (qui correspond à la première MatCard), c'est-à-dire personalInfoForm
.
Je vous propose d'utiliser formGroupName
plutôt que [formGroup]
. Puisque vous êtes à l'intérieur de mainForm
, et que mainForm
a un sous-groupe personalInfo
, vous pouvez faire :
<mat-card class="form-card" formGroupName="personalInfo">
<mat-card-subtitle>Informations personnelles</mat-card-subtitle>
</mat-card>
Ensuite, vous allez générer une div
avec deux contrôles, avec cette commande Emmet :
.form-row>(mat-form-field>mat-label+input)*2
Ce qui produit :
<div class="form-row">
<mat-form-field>
<mat-label></mat-label>
<input type="text">
</mat-form-field>
<mat-form-field>
<mat-label></mat-label>
<input type="text">
</mat-form-field>
</div>
Il ne reste plus qu'à ajouter les textes pour les labels, ainsi que les directives pour les contrôles et leur apparence, et vous avez :
<mat-card class="form-card" formGroupName="personalInfo">
<mat-card-subtitle>Informations personnelles</mat-card-subtitle>
<div class="form-row">
<mat-form-field appearance="fill">
<mat-label>Prénom</mat-label>
<input type="text" matInput formControlName="firstName">
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Nom</mat-label>
<input type="text" matInput formControlName="lastName">
</mat-form-field>
</div>
</mat-card>
Pour la MatCard "Email", vous pouvez faire plus ou moins la même chose, avec une petite nuance :
<mat-card class="form-card" [formGroup]="emailForm">
<mat-card-subtitle>Email</mat-card-subtitle>
<div class="form-row">
<mat-form-field appearance="fill">
<mat-label>Adresse mail</mat-label>
<input type="text" matInput formControlName="email">
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Confirmer votre adresse mail</mat-label>
<input type="text" matInput formControlName="confirm">
</mat-form-field>
</div>
</mat-card>
Dans ce cas, au lieu d'utiliser formGroupName
, j'ai choisi de lier la MatCard au FormGroup emailForm
directement.
Pour la MatCard "Téléphone", vous pouvez lier l' input
directement au FormControl (ou utiliser formControlName
si vous préférez, les deux fonctionnent) :
<mat-card class="form-card">
<mat-card-subtitle>Téléphone</mat-card-subtitle>
<mat-form-field appearance="fill">
<mat-label>Numéro de téléphone</mat-label>
<input type="text" matInput [formControl]="phoneCtrl">
</mat-form-field>
</mat-card>
La MatCard "Informations de connexion" comportera le contrôle "Nom d'utilisateur" sur une ligne, et les deux contrôles "Mot de passe" sur la ligne suivante :
<mat-card class="form-card" [formGroup]="loginInfoForm">
<mat-card-subtitle>Informations de connexion</mat-card-subtitle>
<mat-form-field appearance="fill">
<mat-label>Nom d'utilisateur</mat-label>
<input type="text" matInput formControlName="username">
</mat-form-field>
<div class="form-row">
<mat-form-field appearance="fill">
<mat-label>Mot de passe</mat-label>
<input type="password" matInput formControlName="password">
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Confirmer votre mot de passe</mat-label>
<input type="password" matInput formControlName="confirmPassword">
</mat-form-field>
</div>
</mat-card>
Du coup, il ne reste plus que contactPreference
:
<mat-card class="form-card">
<mat-card-subtitle>Comment préférez-vous être contacté(e) ?</mat-card-subtitle>
<mat-radio-group [formControl]="contactPreferenceCtrl">
<mat-radio-button value="email">Mail</mat-radio-button>
<mat-radio-button value="phone">Téléphone</mat-radio-button>
</mat-radio-group>
</mat-card>
Voici le fonctionnement des boutons radio Material :
chaque option correspond à un
mat-radio-button
avec unevalue
, le tout dans unmat-radio-group
;la liaison du FormControl se fait au niveau du
mat-radio-group
.
Le template du formulaire est donc prêt :
Implémentez temporairement onSubmitForm()
:
onSubmitForm() {
console.log(this.mainForm.value);
}
Maintenant, si vous remplissez le formulaire et que vous l'envoyez, vous verrez le résultat de la structure du formulaire final :
En résumé
Créez des FormControls indépendants avec
FormBuilder.control
lorsque vous avez besoin d'un accès facile à un contrôle.Placez des FormControls créés indépendamment dans des FormGroups en les passant à
FormBuilder.group
, pour profiter aussi des avantages liés aux FormGroups (validation entre plusieurs champs, récupération des valeurs sous forme d'objet, structure simplifiée dans le DOM…).Utilisez
[formGroup]
,formGroupName
,[formControl]
etformControlName
pour lier les différentes structures au template.
Vous voilà maintenant avec un formulaire complexe au niveau de sa structure. Dans le prochain chapitre, vous allez lui apporter du comportement dynamique pour l'améliorer !