Si vous êtes habitué à travailler avec les formulaires réactifs (Reactive forms) et à créer de nombreux formulaires, vous avez probablement remarqué que cela prend du temps et que vous répétez souvent les mêmes étapes. Aujourd'hui, après avoir testé et exploré plusieurs façons de simplifier cela, j'ai décidé de créer ma propre version de formulaire dynamique. Bien sûr, il existe déjà des solutions existantes, mais j'ai choisi de m'appuyer sur certains concepts pour créer mon propre composant dynamique.
Code source disponible sur gitLab https://gitlab.com/eroamba/dynamic_form
Introduction
Notre formulaire est dynamique et entièrement réutilisable à 100 %. Il est simple à configurer et offre une grande flexibilité. Dans un prochain article, nous explorerons une version améliorée de ce formulaire dynamique.
Prérequis
Nous utiliserons la version @angular/cli@16.1.0 pour notre cas pratique. Avant de commencer, assurez-vous d'ajouter Bootstrap à votre projet en ajoutant les liens suivants dans le fichier index.html :
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
Ici Bootstrap est utilisé pour gérer le positionnement des éléments du formulaire avec les classes CSS.
Création du projet
Commencez par générer un nouveau projet Angular en utilisant la commande suivante :
ng new dynamic_form
Création du composant dynamique
Nous allons créer un composant qui contiendra tous nos champs de formulaire désirés. Dans notre cas, nous incluons la plupart des types de champs courants tels que le texte, l'e-mail, la date, le nombre, les cases à cocher, les sélecteurs et les zones de texte. Vous pouvez ajouter d'autres types selon vos besoins.
Commencez par générer le composant dynamic-input avec la commande suivante :
ng generate component dynamic-input
Ensuite, dans le fichier dynamic-input.component.ts, importez les modules nécessaires et définissez les propriétés et méthodes nécessaires :
import { CommonModule } from '@angular/common';
import { Component, Input, Optional, Self } from '@angular/core';
import { ControlContainer, ControlValueAccessor, FormGroup, FormGroupDirective, FormsModule, NgControl, ReactiveFormsModule } from '@angular/forms';
export const NOOP_VALUE_ACCESSOR: ControlValueAccessor =
{
writeValue(): void { },
registerOnChange(): void { },
registerOnTouched(): void { }
};
@Component({
selector: 'app-dynamic-input',
standalone: true,
templateUrl: './dynamic-input.component.html',
styleUrls: ['./dynamic-input.component.css'],
imports: [CommonModule, FormsModule, ReactiveFormsModule],
viewProviders: [{
provide: ControlContainer,
useExisting: FormGroupDirective
}]
})
export class DynamicInputComponent {
@Input() checkedValue: boolean = true
@Input() data: any[]=[]
@Input() dynamicForm: FormGroup=new FormGroup({});
constructor(@Self() @Optional() public ngControl: NgControl) {
if (this.ngControl) {
this.ngControl.valueAccessor = NOOP_VALUE_ACCESSOR;
}
}
getErrorMessage(control: { name: any; validations: any; }) {
const formControl = this.dynamicForm.get(control.name);
if (formControl) {
for (let validation of control.validations) {
if (formControl.hasError(validation.name)) {
return validation.message;
}
}
}
return '';
}
}
Cela permettra au composant dynamic-input d'accéder au FormGroup parent.
viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
Dans notre composant dynamic-input, le fichier HTML est crucial pour afficher dynamiquement les différents types de champs de formulaire en fonction des données fournies. Jetons un coup d'œil au code HTML du composant :
<div class="row">
<ng-container *ngFor="let item of data" [ngSwitch]="item.type">
<div *ngSwitchCase="'text'" [class]="item.classContainer">
<label *ngIf="item.label" [for]="item.id">{{item.label}}</label>
<div class="form-group mb-3">
<input [type]="item.type" [id]="item.id" [formControlName]="item.formControlName" class="form-control"
[placeholder]="item.placeholder" />
<span *ngIf="dynamicForm.get(item.formControlName)?.invalid && dynamicForm.get(item.formControlName)?.touched">
{{ getErrorMessage(item) }}
</span>
</div>
</div>
<div *ngSwitchCase="'email'" [class]="item.classContainer">
<label *ngIf="item.label" [for]="item.id">{{item.label}}</label>
<div class="form-group mb-3">
<input [type]="item.type" [id]="item.id" [formControlName]="item.formControlName" class="form-control"
[placeholder]="item.placeholder" />
<span *ngIf="dynamicForm.get(item.formControlName)?.invalid && dynamicForm.get(item.formControlName)?.touched">
{{ getErrorMessage(item) }}
</span>
</div>
</div>
<div *ngSwitchCase="'date'" [class]="item.classContainer">
<label *ngIf="item.label" [for]="item.id">{{item.label}}</label>
<div class="form-group mb-3">
<input [type]="item.type" [id]="item.id" [formControlName]="item.formControlName" class="form-control"
[placeholder]="item.placeholder" />
<span *ngIf="dynamicForm.get(item.formControlName)?.invalid && dynamicForm.get(item.formControlName)?.touched">
{{ getErrorMessage(item) }}
</span>
</div>
</div>
<div *ngSwitchCase="'select'" [class]="item.classContainer">
<div class="form-group">
<label [for]="item.id">{{item.label}}</label>
<select *ngIf="item.isMultiple && !item.isKeyValue" class="form-control" [id]="item.id" [formControlName]="item.formControlName"
multiple>
<option value="" disabled selected>{{item.placeholder}}</option>
<option *ngFor="let option of item.options" [value]="option">{{option}}</option>
</select>
<select *ngIf="!item.isMultiple && !item.isKeyValue" class="form-control" [id]="item.id" [formControlName]="item.formControlName">
<option value="" disabled selected>{{item.placeholder}}</option>
<option *ngFor="let option of item.options" [value]="option">{{option}}</option>
</select>
<select *ngIf="!item.isMultiple && item.isKeyValue && item.isKeyValue==true" class="form-control" [id]="item.id" [formControlName]="item.formControlName">
<option value="" disabled selected>{{item.placeholder}}</option>
<option *ngFor="let option of item.options" [value]="option.id">{{option.name}}</option>
</select>
</div>
</div>
<div *ngSwitchCase="'number'" [class]="item.classContainer">
<label *ngIf="item.label" [for]="item.id">{{item.label}}</label>
<div class="form-group mb-3">
<input [type]="item.type" [id]="item.id" [formControlName]="item.formControlName" class="form-control"
[placeholder]="item.placeholder" />
<span *ngIf="dynamicForm.get(item.formControlName)?.invalid && dynamicForm.get(item.formControlName)?.touched">
{{ getErrorMessage(item) }}
</span>
</div>
</div>
<div *ngSwitchCase="'radio'">
<label class="pl-3" *ngIf="item.label" [for]="item.id">{{ item.label }}</label>
<div [class]="item.classContainer" style="align-items: center;">
<div class="custom-control custom-radio mr-4">
<input id="false" type="radio" class="custom-control-input " [value]="false" [name]="item.formControlName"
[formControlName]="item.formControlName">
<label class="custom-control-label" for="true">{{item.labelRadio1}}</label>
</div>
<div class="custom-control custom-radio">
<input id="true" type="radio" class="custom-control-input" [value]="true" [name]="item.formControlName"
[formControlName]="item.formControlName">
<label class="custom-control-label" for="false">{{item.labelRadio2}}</label>
</div>
</div>
</div>
<div *ngSwitchCase="'textarea'" [class]="item.classContainer">
<label *ngIf="item.label" [for]="item.id">{{item.label}}</label>
<div class="form-group mb-3">
<textarea [id]="item.id" [formControlName]="item.formControlName" class="form-control" id="description"
[placeholder]="item.placeholder" rows="3"></textarea>
<span *ngIf="dynamicForm.get(item.formControlName)?.invalid && dynamicForm.get(item.formControlName)?.touched">
{{ getErrorMessage(item) }}
</span>
</div>
</div>
</ng-container>
</div>
Ce code génère dynamiquement les champs de formulaire en fonction des données fournies dans data. Chaque élément de data est passé à travers une boucle ngFor, et en fonction du type de champ de formulaire spécifié dans item.type, le composant affiche le champ de formulaire correspondant. Cette approche nous permet d'avoir un composant de formulaire flexible et réutilisable pour une variété de cas d'utilisation.
Après avoir créé notre composant dynamic-input, nous devons l'importer dans notre module principal AppModule pour pouvoir l'utiliser dans notre application. Voici comment vous pouvez le faire :
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
DynamicInputComponent,
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
DynamicInputComponent est un standalone component c’est pour celà nous l’avons mis dans imports et non dans déclarations.
Pour organiser notre projet de manière propre et maintenable, nous allons créer un dossier form-data où nous stockerons nos fichiers de formulaire au format JSON. Dans ce dossier, nous aurons un fichier nommé role.ts qui contiendra notre formulaire de rôle. Voici à quoi cela pourrait ressembler :
Aussi pour member.ts
Pour terminer, voyons comment intégrer notre composant DynamicInputComponent dans notre composant principal de l'application.
Dans le composant principal (app.component.html), nous utilisons le DynamicInputComponent pour créer des formulaires dynamiques pour deux types d'utilisateurs : "Type utilisateur" et "Membre". Nous organisons ces deux types d'utilisateurs dans des onglets utilisant la bibliothèque Bootstrap.
la partie ts
En conclusion, notre composant de formulaire dynamique fonctionne parfaitement, et nous avons la possibilité de personnaliser son apparence en utilisant du CSS pour positionner les champs selon nos besoins. Cette approche nous permet de créer des formulaires dynamiques qui s'adaptent à différents scénarios d'utilisation.
Cependant, malgré sa flexibilité, je trouve que cette approche a ses limites. Dans notre prochain article, nous explorerons une version améliorée de notre composant dynamique, visant à le rendre encore plus flexible et adaptable à une plus grande variété de cas d'utilisation.
Restez à l'écoute pour la prochaine version de notre composant dynamique de formulaire, et n'hésitez pas à expérimenter et à personnaliser celui-ci selon vos besoins spécifiques
#angular Angular #ngConf ng-conf Deborah Kurata John Papa
Code source disponible sur gitLab https://gitlab.com/eroamba/dynamic_form