Écoutez attentivement vos utilisateurs avec les reactive forms

Pour des applications web modernes, vous aurez besoin de formulaires complexes et dynamiques. Angular vous propose les formulaires réactifs.

Là où les formulaires template sont définis par le contenu HTML du template, les formulaires réactifs sont d'abord générés en TypeScript – on vient ensuite relier les différentsinputdu template à l'objet du formulaire.

Les formulaires réactifs offrent beaucoup plus de possibilités aux développeurs :

  • comme leur nom l'indique, les formulaires réactifs mettent à disposition des Observables pour réagir en temps réel aux valeurs entrées par l'utilisateur ;

  • les formulaires réactifs permettent une validation beaucoup plus approfondie ;

  • pour générer des formulaires totalement dynamiques – c'est-à-dire des formulaires dont vous ne connaissez pas la structure en amont – les formulaires réactifs sont la seule solution.

Dans l'application, vous allez créer un formulaire réactif qui permet aux utilisateurs de demander qu’ils soient recontactés !

Placez-vous sur la branche chapitre-3/début et vous aurez un formulaire de contact comme celui-ci :

Un formulaire avec des champs nom, email, objectifs…
Le formulaire de contact

Créez le formulaire

Commencez par ouvrir le fichierapp/home/contact/contact.component.ts. Vous allez commencer par rendre disponibles les différents éléments dont vous aurez besoin :

  • ajoutezReactiveFormsModuleauximports

  • injectezFormBuilderdans une variable privée

import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-contact',
  imports: [
    ReactiveFormsModule
  ],
  styleUrls: ['contact.component.scss'],
  templateUrl: './contact.component.html',
})
export class ContactComponent {
  private formBuilder = inject(FormBuilder);
}

Puis, vous allez utiliser leFormBuilderpour construire votre formulaire :

private formBuilder = inject(FormBuilder);
contactForm = this.formBuilder.group({
  name: [''],
  email: [''],
  course: [''],
  message: ['']
});

Vous utilisez la méthodegroupduFormBuilder, en lui passant un objet :

  • les clés de l'objet correspondent aux noms des champs – ici, j'ai choisi les quatre champs du formulaire de contact ;

  • les valeurs de l'objet correspondent à la configuration de chaque champ – pour l'instant, vous passez uniquement un string vide pour dire que la valeur par défaut de ces champs est''.

Créez dès maintenant une première version de la méthode qui enverra le contenu du formulaire et branchez-la au bouton :

onSubmitForm(): void {
  console.log(this.contactForm.value);
}
<button type="submit" class="submit-button" (click)="onSubmitForm()">
    Begin Your Journey
</button>

Comme avec les formulaires template, vous accédez au contenu du formulaire avec l'attributvalue.

Branchez le formulaire

Pour brancher votre formulaire réactif (qui est de typeFormGroup) au template, il faut l'assigner à l'élémentformvia sa directiveformGroup:

<form id="contact-form" [formGroup]="contactForm">

Il faut ensuite ajouter un attributformControlNameà chaque input du formulaire : ces noms doivent correspondre exactement aux noms des contrôles créés avec FormBuilder :

<div class="form-group">
	<label for="name">Full Name</label>
	<input type="text" id="name" name="name" formControlName="name" placeholder="Enter your name">
</div>

<div class="form-group">
	<label for="email">Email Address</label>
	<input type="email" id="email" name="email" formControlName="email" placeholder="you@example.com">
</div>

<div class="form-group">
	<label for="course">Preferred Course</label>
	<select id="course" name="course" formControlName="course">
		<option value="html">HTML & Web Foundations</option>
		<option value="css">Modern CSS & Design</option>
		<option value="javascript">JavaScript Mastery</option>
	</select>
</div>

@if (selectedCourse$ | async; as course) {
	<app-course-card [course]="course"/>
}

<div class="form-group">
	<label for="message">Your Goals</label>
	<textarea id="message" name="message" rows="4" formControlName="message" placeholder="Tell us what you want to achieve..."></textarea>
</div>

Ainsi, si vous remplissez le formulaire et cliquez sur Begin Your Journey :

Le formulaire rempli avec un nom, un mail, le cours choisi et les objectifs.
Le formulaire rempli
L'objet FormGroup a enregistré toutes les informations du formulaire.
L'objet FormGroup

Observez le formulaire

Pour avoir un premier aperçu du côté "réactif" des formulaires réactifs, je vous propose de réagir au choix du "cours préféré" pour l'afficher au moment de la sélection par l'utilisateur.

Pour commencer, vous aurez besoin deCoursesServicepour récupérer le cours qui correspond au choix de l'utilisateur :

export class ContactComponent {
  private formBuilder = inject(FormBuilder);
  private coursesService = inject(CoursesService);
  // ...

L'Observable dont vous allez profiter pour implémenter ce comportement estvalueChanges. Nous allions créer ensemble, pas par pas, un ObservableselectedCourse$qui se base dessus :

contactForm = this.formBuilder.group({
    name: [''],
    email: [''],
    course: [''],
    message: ['']
});
selectedCourse$ = this.contactForm.valueChanges;

Pour visualiser les émissions de cet Observable, je vous propose de créer un constructor temporaire pour y ajouter unconsole.log :

constructor() {
    this.selectedCourse$.pipe(
      takeUntilDestroyed(),
      tap(console.log)
    ).subscribe();
}

Commencez à entrer des données dans le formulaire, et vous verrez que cet Observable émet à chaque modification du formulaire :

De nombreuses émissions, chacune correspondant à la moindre modification du formulaire
Emissions all the way down

Si on veut seulement les changements de sélection du cours préféré, il va falloir ajouter quelques opérateurs sur cet Observable ! Voilà ce que je propose :

selectedCourse$ = this.contactForm.valueChanges.pipe(
    map(form => form.course),
    filter(course => course !== ''),
    distinctUntilChanged(),
);

Avec ces opérateurs :

  • on map chaque émission vers sa propriétécourse

  • on filtre pour seulement recevoir des émissions où uncourseest sélectionné (car il n'est plus le string vide)

  • distinctUntilChanged, si vous ne le connaissez pas, ne laisse passer une émission que lorsque sa valeur est différente de celle de l'émission précédente : ça évite de recevoir plusieurs fois de suite (de manière redondante) la même valeur

Maintenant si vous jouez un peu avec le formulaire, vous avez :

Seules les modifications de sélection du cours préféré passent le filtre
Le choix de cours

Vous pouvez maintenant utiliserCoursesServicepour récupérer l'objetCoursequi correspond à ces valeurs :

selectedCourse$ = this.contactForm.valueChanges.pipe(
    map(form => form.course),
    filter(course => course !== ''),
    distinctUntilChanged(),
    map(course => this.coursesService.getCourseByType(course)),
);

Avec ça, vous êtes en capacité d'afficher le cours sélectionné. Dans le template, sous l'élément select :

<div class="form-group">
	<label for="course">Preferred Course</label>
	<select id="course" name="course" formControlName="course">
		<option value="html">HTML & Web Foundations</option>
		<option value="css">Modern CSS & Design</option>
		<option value="javascript">JavaScript Mastery</option>
	</select>
</div>

@if (selectedCourse$ | async; as course) {
	<app-course-card [course]="course"/>
}

Ici, vous utilisez le pipe  async  pour souscrire à l'Observable, et la syntaxe  as course  pour générer un alias pour les émissions de l'Observable. Cet alias peut être utilisé, à l'intérieur du bloc  @if, pour faire référence aux émissions. Ainsi, vous n'avez pas besoin d'y re-souscrire avec le pipe  async(ce qui, n'oubliez pas, en générerait une nouvelle instance).

Maintenant, lorsque l'utilisateur sélectionne un cours, il le voit en temps réel dans le formulaire !

Lorsque l'utilisateur sélectionne un cours, une carte de résumé du cours apparaît sous sa sélection
Aperçu du cours sélectionné

À vous de jouer

Contexte

On souhaite permettre à l'utilisateur de créer et de mettre à jour son profil, tout en lui en fournissant un aperçu en temps réel.

Les routes, components, et services ont déjà été créés. Cliquez sur Profile dans l'en-tête de l'application pour y accéder. Il ne reste plus qu'à brancher un formulaire réactif !

Consignes

Vous allez créer et brancher un formulaire réactif au formulaire existant dans app/profile/profile-form.

  • pour fonctionner avec le service en place, les champs devront avoir les noms  name, title, bio, website, github  .

  • créez le  FormGroup  avec ses contrôles, puis branchez le tout aux éléments du template

  • observez les modifications du formulaire, et pour chaque modification, appelez la méthode  onUpdateProfile  fournie en y passant la valeur du formulaire

  • nous sommes dans un cas de figure où on n'a pas le choix d'utiliser le pipe  async  pour souscrire : il faudra donc  subscribedans le  constructor  à l'Observable que vous avez créé

    • quand on appelle  subscribe  , il faut systématiquement penser à la stratégie de unsubscribe !

Corrigé

En résumé

  • Ajoutez ReactiveFormsModule aux imports d'AppModule pour débloquer les formulaires réactifs ;

  • Utilisez FormBuilder pour générer un objet de type FormGroup ;

  • Liez le formdu template au FormGroup avec[formGroup], et lesinputdu formulaire aux contrôles du FormGroup avecformControlName ;

  • Observez les changements de valeur du formulaire avec son ObservablevalueChanges.

Vous le savez surement, mais il ne faut jamais faire confiance aux valeurs fournies par un utilisateur : c'est pourquoi le sujet du prochain chapitre est la validation !

Et si vous obteniez un diplôme OpenClassrooms ?
  • Formations jusqu’à 100 % financées
  • Date de début flexible
  • Projets professionnalisants
  • Mentorat individuel
Trouvez la formation et le financement faits pour vous