Appearance
FormArray : gérer des champs de formulaire dynamiques
Les FormArray
sont particulièrement utiles lorsque vous devez gérer une liste dynamique de champs de formulaire. Imaginez que vous créez une liste de courses : vous ne savez pas à l'avance combien d'articles vous allez ajouter, et vous voulez pouvoir en ajouter ou en supprimer facilement.
Comprendre par l'exemple
Prenons un exemple concret : vous gérez un formulaire d'inscription à un événement où les participants peuvent ajouter plusieurs invités. Chaque invité a un nom et une adresse email.
Idée principale
FormArray
est idéal quand vous ne connaissez pas à l'avance le nombre d'éléments que l'utilisateur va ajouter.
Voici comment implémenter cela :
ts
import { Component, inject } from '@angular/core';
import {
FormArray,
FormBuilder,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
@Component({
selector: 'app-event-form',
standalone: true,
templateUrl: './event-form.component.html',
imports: [ReactiveFormsModule],
})
export class EventFormComponent {
private fb = inject(FormBuilder);
eventForm = this.fb.group({
eventName: ['', Validators.required],
guests: this.fb.array([]),
});
get guests() {
return this.eventForm.get('guests') as FormArray;
}
addGuest() {
const guestForm = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
});
this.guests.push(guestForm);
}
removeGuest(index: number) {
this.guests.removeAt(index);
}
onSubmit() {
if (this.eventForm.valid) {
console.log(this.eventForm.value);
}
}
}
angular-html
<form [formGroup]="eventForm" (ngSubmit)="onSubmit()">
<div>
<label for="eventName">Nom de l'événement</label>
<input id="eventName" formControlName="eventName" />
</div>
<div formArrayName="guests">
@for (guest of guests.controls; track $index) {
<div [formGroupName]="$index">
<h3>Invité #{{ $index + 1 }}</h3>
<div>
<label>Nom:</label>
<input formControlName="name" />
</div>
<div>
<label>Email:</label>
<input formControlName="email" />
</div>
<button type="button" (click)="removeGuest($index)">
Supprimer l'invité
</button>
</div>
}
</div>
<button type="button" (click)="addGuest()">Ajouter un invité</button>
<button type="submit" [disabled]="!eventForm.valid">Enregistrer</button>
</form>
Je vais continuer l'explication du FormArray.
Comment fonctionne FormArray ?
Analysons chaque partie :
1. Création du FormArray
STRUCTURE
Un FormArray est toujours une propriété d'un FormGroup parent. Il peut contenir des FormControl, FormGroup ou même d'autres FormArray.
ts
eventForm = this.fb.group({
eventName: ['', Validators.required],
guests: this.fb.array([]), // FormArray vide au départ
});
2. Accès au FormArray
Pour manipuler facilement le FormArray, on crée un getter :
ts
get guests() {
return this.eventForm.get('guests') as FormArray;
}
3. Ajout d'éléments
Quand on ajoute un élément, on crée généralement un nouveau FormGroup
:
ts
const guestForm = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
});
this.guests.push(guestForm);
4. Structure HTML
La directive formArrayName
permet de lier un élément HTML à un FormArray
dans votre formulaire réactif. Elle agit comme un pont entre votre template et la définition du FormArray
dans votre composant.
Voici comment elle fonctionne :
ts
eventForm = this.fb.group({
eventName: [''],
guests: this.fb.array([]) // 'guests' est le nom qu'on utilisera dans formArrayName
});
angular-html
<div formArrayName="guests">
<!-- Ici, Angular sait que 'guests' fait référence au FormArray -->
<!-- Le contenu de cette div a accès au contexte du FormArray -->
</div>
[formGroupName]="$index"
permet d'accéder à chaque FormGroup individuel dans le FormArray. L'index est crucial car il indique à Angular quel élément du tableau nous manipulons.
Voici un exemple détaillé :
ts
// Dans le composant, chaque élément du FormArray est un FormGroup
addGuest() {
const guestForm = this.fb.group({
name: [''],
email: ['']
});
this.guests.push(guestForm);
}
angular-html
<div formArrayName="guests">
@for (guest of guests.controls; track $index) {
<!-- $index représente la position dans le tableau (0, 1, 2, etc.) -->
<div [formGroupName]="$index">
<!-- Maintenant nous sommes dans le contexte du FormGroup spécifique -->
<input formControlName="name">
<input formControlName="email">
</div>
}
</div>
Hiérarchie des directives
La structure hiérarchique est importante :
angular-html
<form [formGroup]="eventForm"> <!-- Niveau 1: FormGroup principal -->
<div formArrayName="guests"> <!-- Niveau 2: FormArray -->
<div [formGroupName]="$index"> <!-- Niveau 3: FormGroup individuel -->
<input formControlName="name"> <!-- Niveau 4: FormControl -->
</div>
</div>
</form>
ATTENTION
Les directives doivent respecter cette hiérarchie. Si vous oubliez un niveau ou changez l'ordre, Angular lancera une erreur.
Accès aux valeurs et états
Vous pouvez accéder aux valeurs et états à différents niveaux :
ts
// Accès à tout le FormArray
console.log(this.guests.value);
// Accès à un FormGroup spécifique
console.log(this.guests.at(0).value);
// Accès à un contrôle spécifique
console.log(this.guests.at(0).get('name').value);
// Vérification de la validité
console.log(this.guests.valid); // Vérifie tout le FormArray
console.log(this.guests.at(0).valid); // Vérifie un FormGroup spécifique
ASTUCE
Utilisez la méthode at()
plutôt que l'accès direct par index (controls[index]
) car elle est plus sûre et typée.
5. Méthodes utiles du FormArray
Voici les principales méthodes que vous pouvez utiliser avec FormArray :
ts
// Ajouter un contrôle à la fin
this.guests.push(newFormGroup);
// Ajouter un contrôle à une position spécifique
this.guests.insert(index, newFormGroup);
// Supprimer un contrôle à un index
this.guests.removeAt(index);
// Supprimer tous les contrôles
this.guests.clear();
// Obtenir le nombre de contrôles
const length = this.guests.length;
Exemple pratique : Validation du nombre d'invités
Voici comment ajouter une validation sur le nombre d'invités :
ts
addGuest() {
if (this.guests.length < 5) { // Maximum 5 invités
const guestForm = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
});
this.guests.push(guestForm);
}
}
angular-html
<button
type="button"
(click)="addGuest()"
[disabled]="guests.length >= 5">
Ajouter un invité
</button>
@if (guests.length >= 5) {
<p class="error">Maximum 5 invités autorisés</p>
}
Accès aux valeurs
Pour récupérer toutes les valeurs du FormArray :
ts
onSubmit() {
if (this.eventForm.valid) {
const guestList = this.guests.value; // Tableau des valeurs
console.log(guestList);
// [{name: "John", email: "[email protected]"}, ...]
}
}