• 8 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/31/22

É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érents  input  du 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 Snapface, vous allez créer un formulaire réactif qui permet enfin aux utilisateurs d'enregistrer leurs propres FaceSnaps !

Importez  ReactiveFormsModule

Comme pour les formulaires template, il faut ajouter un import à AppModule. Pour les formulaires réactifs, il faut importer  ReactiveFormsModule  :

// ...
imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    ReactiveFormsModule
],
//...

Préparez le component

Vous allez créer un nouveau component pour le formulaire, auquel vos utilisateurs accéderont par une nouvelle route. Générez un nouveau component appelé NewFaceSnapComponent avec le CLI :

ng g c new-face-snap

Ajoutez une nouvelle route  create  à votre application :

//...
const routes: Routes = [
  { path: 'facesnaps/:id', component: SingleFaceSnapComponent },
  { path: 'facesnaps', component: FaceSnapListComponent },
  { path: 'create', component: NewFaceSnapComponent },
  { path: '', component: LandingPageComponent }
];
//...

Je vous propose de créer un bouton dans HeaderComponent pour la création des nouveaux FaceSnaps. Commencez par injecter le Router et par créer la méthode qui effectuera la redirection :

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {

  constructor(private router: Router) { }

  ngOnInit(): void {
  }

  onAddNewFaceSnap() {
    this.router.navigateByUrl('/create');
  }
}

Puis ajoutez le bouton au template :

<header>
  <h1>snapface</h1>
  <nav>
    <a routerLink="" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Home</a>
    <a routerLink="facesnaps" routerLinkActive="active">FaceSnaps</a>
  </nav>
  <button (click)="onAddNewFaceSnap()">+</button>
</header>

Un clic sur votre nouveau bouton affiche bien "new-face-snap works!".

Créez le formulaire

Tout d'abord, vous allez déclarer la variable qui contiendra l'objet du formulaire. Son type est FormGroup  (et non NgForm  comme pour les formulaires template) :

import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'app-new-face-snap',
  templateUrl: './new-face-snap.component.html',
  styleUrls: ['./new-face-snap.component.scss']
})
export class NewFaceSnapComponent implements OnInit {

  snapForm!: FormGroup;

  constructor() { }

  ngOnInit(): void {
  }

}

Ensuite, vous allez injecter un outil qui simplifie largement la génération des formulaires réactifs, le FormBuilder  :

//...
import { FormBuilder, FormGroup } from '@angular/forms';
//...
constructor(private formBuilder: FormBuilder) { }
//...

Puis, dans ngOnInit()  , vous allez utiliser le  FormBuilder  pour construire votre formulaire :

ngOnInit(): void {
    this.snapForm = this.formBuilder.group({
        title: [null],
        description: [null],
        imageUrl: [null],
        location: [null]
    });
}

Vous utilisez la méthode  group  du FormBuilder, en lui passant un objet :

  • les clés de l'objet correspondent aux noms des champs – ici, j'ai choisi les quatre champs du modèle FaceSnap que l'utilisateur pourra entrer ;

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

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

onSubmitForm() {
    console.log(this.snapForm.value);
}

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

Branchez le formulaire

Il est temps maintenant de créer le formulaire dans le template et de le brancher à  snapForm  . Je vous propose de mettre le formulaire dans une "card" comme celles des FaceSnaps. Dans  new-face-snap.component.scss  :

.form-card {
  box-sizing: border-box;
  width: 50%;
  margin: 20px auto;
  padding: 10px 30px;
  box-shadow: lightgray 4px 4px 20px;
}

.form-group {
  margin: 10px auto;
  width: 80%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

input, textarea {
  width: 50%;
}

.action-buttons {
  width: 100%;
}

button {
  display: block;
  margin: 20px auto;
}

Et maintenant, dans le template :

<div class="form-card">
    <h2>NOUVEAU FACESNAP</h2>
    <form [formGroup]="snapForm">
    
    </form>
</div>

Il faut attribuer un  formControlName  à chaque input que vous ajouterez à ce formulaire : ces noms doivent correspondre aux noms des contrôles créés avec FormBuilder :

<form [formGroup]="snapForm">   
    <div class="form-group">
        <label for="title">Titre</label>
        <input id="title" type="text" formControlName="title">
    </div>
    <div class="form-group">
        <label for="description">Description</label>
        <textarea id="description" type="text" formControlName="description" rows="4">
        </textarea>
    </div>
    <div class="form-group">
        <label for="imageUrl">URL de l'image</label>
        <input id="imageUrl" type="text" formControlName="imageUrl">
    </div>
    <div class="form-group">
        <label for="location">Lieu</label>
        <input id="location" type="text" formControlName="location">
    </div>
    <div class="action-buttons">
        <button type="submit" (click)="onSubmitForm()">Enregistrer</button>
    </div>
</form>

Ainsi, si vous remplissez le formulaire et cliquez sur Enregistrer :

Le formulaire rempli avec un titre, une description, l'URL de l'image et le lieu.
Le formulaire rempli
L'objet FormGroup a enregistré toutes les informations du formulaire : le titre, la description, l'URL de l'image et le lieu.
L'objet FormGroup

Observez le formulaire

Pour avoir un premier aperçu du côté "réactif" des formulaires réactifs, je vous propose d'afficher en temps réel le FaceSnap que l'utilisateur est en train de créer.

Commencez par ajouter ces styles de NewFaceSnapComponent (ajoutez  .face-snap-card  à la première ligne et ajoutez la classe  .face-snap-card  en dessous, tout en laissant les autres styles du fichier en place) :

.form-card, .face-snap-card {
  box-sizing: border-box;
  width: 50%;
  margin: 20px auto;
  padding: 10px 30px;
  box-shadow: lightgray 4px 4px 20px;
}

.face-snap-card {
  img {
    width: 100%;
  }
  h2 {
    margin-bottom: 0;
  }
  p {
    font-weight: 300;
    font-size: 16px;
  }
}
//...

Dans le TypeScript, vous allez créer un Observable  faceSnapPreview$  qui émettra des objets de type  FaceSnap  :

//...
snapForm!: FormGroup;
faceSnapPreview$!: Observable<FaceSnap>;
//...

Branchez cet Observable aux changements de valeur du formulaire avec son attribut  valueChanges  , un Observable qui émet la valeur du formulaire à chaque modification :

this.faceSnapPreview$ = this.snapForm.valueChanges;

Le seul souci ici est que le formulaire n'émet pas des objets de type FaceSnap : il manque des attributs. Il faut donc utiliser l'un des opérateurs que vous avez découverts pour transformer les émissions en FaceSnaps valables – l'opérateur map()  :

this.faceSnapPreview$ = this.snapForm.valueChanges.pipe(
    map(formValue => ({
        ...formValue,
        createdDate: new Date(),
        snaps: 0,
        id: 0
    }))
);

Vous pouvez maintenant utiliser le pipe  async  pour afficher cet aperçu dans le template :

<div class="form-card">
    <!-- ... -->
</div>
<div class="face-snap-card" *ngIf="faceSnapPreview$ | async as faceSnap">
    <h2>{{ faceSnap.title | uppercase }}</h2>
    <p>Mis en ligne {{ faceSnap.createdDate | date: 'à HH:mm, le d MMMM yyyy' }}</p>
    <img [src]="faceSnap.imageUrl" [alt]="faceSnap.title">
    <p>{{ faceSnap.description }}</p>
    <p *ngIf="faceSnap.location">Photo prise à {{ faceSnap.location }}</p>
</div>

Vous voyez ici un pattern très courant et extrêmement utile qui utilise la directive  *ngIf  , le pipe  async  , et le mot-clé as  . Cette approche :

  • souscrit à l'Observable ;

  • ajoute la  <div>  uniquement lorsque l'Observable émet ;

  • crée un alias pour l'émission qui est utilisable à l'intérieur de la  <div>  .

Cet alias permet de traiter les émissions de l'Observable comme si elles étaient des valeurs fixes. C'est ce qui vous permet d'accéder facilement aux attributs du FaceSnap émis sans avoir à y souscrire de nouveau.

Avec tout ça mis en place, lorsque vous remplissez le formulaire, vous voyez l'aperçu se créer devant vos yeux :

L'aperçu du FaceSnap se remplit en même temps avec les informations renseignées : le titre, la description, l'URL de l'image et le lieu.
L'aperçu du FaceSnap

Il s'agit d'un exemple très simple, mais qui vous donne une première idée des possibilités liées aux formulaires réactifs.

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  form  du template au FormGroup avec  [formGroup], et les  input du formulaire aux contrôles du FormGroup avec  formControlName ;

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

Avant de permettre aux utilisateurs d'ajouter leurs FaceSnaps, il faut valider le contenu du formulaire, et ce sera le sujet du prochain chapitre !

Example of certificate of achievement
Example of certificate of achievement