• 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

Préparez le module

Dans cette nouvelle partie du cours, vous allez travailler sur un formulaire complexe.

Qu'est-ce qu'un formulaire complexe ?

Il n'y a pas de définition précise, mais le plus souvent, on considère qu'un formulaire est complexe lorsqu'un FormGroup simple avec des Validators Angular ne suffit pas.

Le formulaire que vous allez créer a des besoins qui vont au-delà d'un formulaire simple. Il s'agit d'un formulaire d'inscription utilisateur :

  • L'utilisateur doit pouvoir choisir s'il préfère être contacté par téléphone ou par mail.

    • Les contrôles appropriés doivent apparaître et disparaître.

    • Lorsqu'un contrôle de contact est actif, il doit être requis, mais lorsqu'il est inactif, il ne doit pas être requis (sinon l'utilisateur ne pourra jamais valider le formulaire !).

  • L'utilisateur choisira son mot de passe et le confirmera dans un deuxième champ.

    • Il faudra créer un Validator qui vérifie que le contenu des deux champs ait la même valeur.

    • Puisque ce Validator joue sur plusieurs contrôles, il faudra l'attribuer à tout un FormGroup et non à un FormControl spécifique.

    • Ce Validator devra être réutilisable, car il sera utilisé pour la confirmation de l'adresse mail.

  • Le formulaire affichera des messages d'erreur spécifiques lorsque l'utilisateur entrera des valeurs invalides.

Pour bien implémenter toutes ces fonctionnalités, le FormGroup final du formulaire sera composé de plusieurs FormGroup et FormControl gérés indépendamment.

Tout cela sera contenu dans un nouveau feature module, donc il est temps de le créer !

Créez le module

À vous de jouer !

Je vous propose de faire les premières préparations vous-même ! Voici les besoins :

  • un nouveau feature module ComplexFormModule ;

  • il doit être lazy-loaded ;

  • il n'aura qu'une seule route et un seul component : ComplexFormComponent ;

  • il faut pouvoir y accéder par un lien dans le header ;

  • ce nouveau module aura besoin de deux nouveaux components Material : MatCheckbox et MatRadio ;

  • dans ComplexFormComponent, il faudra un premier FormGroup appelé  mainForm  .

Voilà, vous avez tout ce qu'il vous faut ! Implémentez tout ce que vous pouvez, et retrouvez le guide ci-dessous.

Suivez ma solution

Commencez par créer le module :

ng g m complex-form --routing

Ajoutez ensuite le nouveau  path  à AppRoutingModule pour le lazy-loading :

{ path: 'complex-form', loadChildren: () => import('./complex-form/complex-form.module').then(m => m.ComplexFormModule) },

Et ajoutez un lien au header pour y accéder :

<li><a routerLink="/complex-form">Complex Form</a></li>

Créez le component qui sera utilisé :

ng g c complex-form/components/complex-form

Et attribuez-le à la route vide de ComplexFormModule dans ComplexFormRoutingModule :

const routes: Routes = [
  { path: '', component: ComplexFormComponent }
];

Ce nouveau module a besoin d'éléments exportés par SharedModule, donc importez-le :

@NgModule({
  declarations: [
    ComplexFormComponent
  ],
  imports: [
    CommonModule,
    ComplexFormRoutingModule,
    SharedModule
  ]
})
export class ComplexFormModule { }

Il faut également deux nouveaux components Material, donc ajoutez MatCheckboxModule et MatRadioModule aux  exports  de votre MaterialModule :

@NgModule({
  exports: [
    MatToolbarModule,
    MatCardModule,
    MatListModule,
    MatButtonModule,
    MatIconModule,
    MatFormFieldModule,
    MatInputModule,
    MatCheckboxModule,
    MatRadioModule
  ]
})
export class MaterialModule {}

Dernière étape : créez un FormGroup appelé  mainForm  dans ComplexFormComponent. Je vous propose de le créer avec FormBuilder, et de le faire via une méthode :

export class ComplexFormComponent implements OnInit {

  mainForm!: FormGroup;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit(): void {

  }

  initMainForm(): void {
    this.mainForm = this.formBuilder.group({});
  }

}

Préparez le template

Tout d'abord, un peu de ménage, car les liens du  header  sont collés ! 

Les deux liens sont collés l'un à l'autre
Pas de place !

Je vous propose donc d'ajouter un style à  header.component.scss  pour régler ce petit bug d'affichage :

li {
  display: inline-block;
  margin-right: 20px;
}

Pour le template de ComplexFormComponent, voici tous les styles dont vous aurez besoin pour cette partie du cours :

mat-form-field {
  width: 45%;
  &.full-width {
    width: 80%;
    margin-left: 10%;
  }
}

.form-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.form-card {
  margin: 20px 0;
}

.mat-radio-button ~ .mat-radio-button {
  margin-left: 16px;
}

mat-card-actions {
  text-align: center;
}

.main-form {
  padding-bottom: 30px;
  position: relative;
}

.loading {
  position: absolute;
  top: -10px;
  bottom: -10px;
  left: -10px;
  right: -10px;
  height: inherit;
  z-index: 10;
  backdrop-filter: blur(5px);
  display: flex;
  justify-content: center;
  align-items: center;
}

.error-text {
  color: red;
}

Avant de générer le template, appelez  initMainForm()  dans  ngOnInit()  :

ngOnInit(): void {
    this.initMainForm();
}

Pour générer plus rapidement le template, je vous propose d'utiliser quelques raccourcis Emmet.

Pour utiliser un raccourci, tapez-en le contenu et appuyez sur Tab (cette touche peut être différente selon les configurations).

Le premier raccourci est  mat-card.main-form  pour générer la MatCard principale :

<mat-card class="main-form"></mat-card>

Ensuite, à l'intérieur de cette MatCard, vous allez d'abord créer le titre principal avec le raccourci  mat-card-title  (+ Tab), et ajouter le texte "Inscription" :

<mat-card class="main-form">
  <mat-card-title>Inscription</mat-card-title>
</mat-card>

Et maintenant, le raccourci de pro !

mat-card.form-card*5>mat-card-subtitle

Ce qui donne :

<mat-card class="main-form">
  <mat-card-title>Inscription</mat-card-title>
  <mat-card class="form-card">
    <mat-card-subtitle></mat-card-subtitle>
  </mat-card>
  <mat-card class="form-card">
    <mat-card-subtitle></mat-card-subtitle>
  </mat-card>
  <mat-card class="form-card">
    <mat-card-subtitle></mat-card-subtitle>
  </mat-card>
  <mat-card class="form-card">
    <mat-card-subtitle></mat-card-subtitle>
  </mat-card>
  <mat-card class="form-card">
    <mat-card-subtitle></mat-card-subtitle>
  </mat-card>
</mat-card>

Plutôt pratique !

Voici le template final :

<mat-card [formGroup]="mainForm" class="main-form">
    <mat-card-title>Inscription</mat-card-title>
    <mat-card class="form-card">
        <mat-card-subtitle>Informationss personnelles</mat-card-subtitle>
    </mat-card>
    <mat-card class="form-card">
        <mat-card-subtitle>Comment préférez-vous que l'on vous contacte ?</mat-card-subtitle>
    </mat-card>
    <mat-card class="form-card">
        <mat-card-subtitle>Email</mat-card-subtitle>
    </mat-card>
    <mat-card class="form-card">
        <mat-card-subtitle>Téléphone</mat-card-subtitle>
    </mat-card>
    <mat-card class="form-card">
        <mat-card-subtitle>Informations de connexion</mat-card-subtitle>
    </mat-card>
    <mat-card-actions *ngIf="mainForm.valid">
        <button mat-flat-button color="primary" (click)="onSubmitForm()">ENREGISTRER</button>
    </mat-card-actions>
  • Vous attribuez le FormGroup  mainForm  à la MatCard principale.

  • Vous créez une section  mat-card-actions  qui n'apparaît que si le formulaire est valide.

  • Vous ajoutez un bouton ENREGISTRER qui appelle une méthode  onSubmitForm()  .

Soit par le biais d'un raccourci IDE, soit manuellement, créez la méthode  onSubmitForm()  dans le component en la laissant vide :

onSubmitForm() {

}

Vous l'implémenterez plus tard !

Le template est prêt :

La base du formulaire
La base du formulaire

Il ne reste plus qu'à créer un service pour communiquer avec le serveur, et un modèle pour englober les données du formulaire !

Terminez l'infrastructure

Vous allez d'abord créer un modèle pour les données venant du formulaire. Dans un dossier models, créez  complex-form-value.model.ts  :

export class ComplexFormValue {
  personalInfo!: {
    firstName: string,
    lastName: string
  };
  contactPreference!: string;
  email?: {
    email: string,
    confirm: string
  };
  phone?: string;
  loginInfo!: {
    username: string,
    password: string,
    confirmPassword: string,
  };
}

Vous remarquerez qu'il y a un sous-objet pour chaque MatCard dans le template, et ce n'est pas par hasard !

Il faudra ensuite un service pour parler avec le serveur. Dans un dossier services, créez  complex-form.service.ts et la méthode qui servira à enregistrer l'utilisateur :

@Injectable()
export class ComplexFormService {
  constructor(private http: HttpClient) {}

  saveUserInfo(formValue: ComplexFormValue): Observable<boolean> {
    return this.http.post(`${environment.apiUrl}/users`, formValue).pipe(
      mapTo(true),
      delay(1000),
      catchError(() => of(false).pipe(
        delay(1000)
      ))
    );
  }
}

Cette méthode :

  • utilisemapTo  pour transformer toute réponse du serveur (et donc émission de l'Observable) en true  , peu importe la valeur de la réponse ;

  • retarde cette réponse d'une seconde pour simuler un délai de réseau ;

  • en cas d'erreur, émet  false  (également retardé d'une seconde).

L'idée avec cette méthode est de recevoir un message  true  pour une requête réussie, ou  false  pour une requête échouée.

Il ne reste plus qu'à ajouter ce nouveau service aux  providers  de ComplexFormModule :

@NgModule({
  declarations: [
    ComplexFormComponent
  ],
  imports: [
    CommonModule,
    ComplexFormRoutingModule,
    SharedModule
  ],
  providers: [
    ComplexFormService
  ]
})
export class ComplexFormModule { }

Et l'infrastructure du module est prête !

En résumé

  • Un formulaire complexe est un formulaire où un seul FormGroup avec les Validators fournis avec Angular ne suffisent pas.

  • Emmet est un générateur de HTML, fourni avec les IDE ou en plugin, qui permet d'écrire du code HTML beaucoup plus rapidement et facilement.

  • catchError  est un opérateur RxJS qui réagit à toute erreur et la transforme en un autre Observable (dans ce cas,  of(false)  ).

Maintenant que vous avez construit le module, il est temps de créer le formulaire. Rendez-vous au prochain chapitre !

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