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 !
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 :
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 :
utilise
mapTo
pour transformer toute réponse du serveur (et donc émission de l'Observable) entrue
, 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 !