• 12 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 20/06/2022

Construisez le formulaire

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 une  value , le tout dans un  mat-radio-group;

  • la liaison du FormControl se fait au niveau du  mat-radio-group  .

Le template du formulaire est donc prêt :

Formulaire complété
Formulaire complé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 :

L'objet final
L'objet 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]   et  formControlName 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 !

Exemple de certificat de réussite
Exemple de certificat de réussite