×

Tutoriel : Créé un formulaire progressif avec Angular et TailwindCSS

Tutoriel : Créé un formulaire progressif avec Angular et TailwindCSS

Tailwind CSS est un framework CSS utilitaire qui permet de concevoir rapidement des interfaces web en utilisant des classes prédéfinies pour styliser les éléments HTML. Dans ce tutoriel, on vas crée un formulaire progressif avec validation avancée en utilisant Angular et Tailwind CSS. Assurez-vous d’installer Angular et Tailwind CSS avant de commencer.

Étape 1 : Créez un projet Angular

Si vous n’avez pas déjà un projet Angular, vous pouvez en créer un à l’aide d’Angular CLI en exécutant la commande suivante :

Bash
ng new formulaire-progressif

Étape 2 : Créez les composants Angular

Créez trois composants Angular pour chaque section du formulaire : InformationPersonnel, InformationProfessionnel, et Resume. Vous pouvez utiliser la commande suivante pour générer les composants :

Bash
ng generate component InformationPersonnel
ng generate component InformationProfessionnel
ng generate component Resume

Étape 3 : Créez un service Angular pour stocker les données

Créez un service Angular qui stockera les données du formulaire à mesure qu’elles sont saisies. Vous pouvez utiliser la commande suivante pour générer un service :

Bash
ng generate service formData

Étape 4 : Installation et configuration de TailwindCSS

Pour installer et configurer Tailwind CSS dans votre projet Angular, suivez ces étapes

  • Installation de Tailwind CSS : Assurez-vous d’être dans le répertoire de votre projet Angular et tapez la commande suivantes :
Bash
npm install tailwindcss
  • Configuration de Tailwind CSS : Créez un fichier de configuration Tailwind CSS en utilisant la commande suivante :
Bash
   npx tailwindcss init

Cela créera un fichier tailwind.config.json dans la racine de votre projet. Vous pouvez personnaliser ce fichier pour définir vos propres couleurs, polices, classes personnalisées, etc. Ensuite, modifier le fichier tailwind.config.json en ajoutant le code suivant :

JavaScript
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": []
  },
  "files": [
    "src/main.ts"
  ],
  "include": [
    "src/**/*.d.ts"
  ]

Étape 5 : Créez le formulaire progressif

Dans chaque composant, créez un formulaire HTML en utilisant Angular Forms (Reactive Forms) pour collecter les informations personnelles, professionnelles et le résumé. Utilisez Tailwind CSS pour styliser le formulaire.

Composant InformationPersonnel : information-personnel.component.html

HTML
<form class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" [formGroup]="form" (ngSubmit)="onNextStep()">
    <h1 class="text-center font-bold text-2xl">Informations Personnel</h1>
    <div class="mb-6">
      <label class="block text-gray-700 text-sm font-bold mb-2">Nom</label>
      <input
        class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        formControlName="nom"
        required
        (input)="validateField('nom')"
        [ngClass]="{ 'border-red-500': isFieldInvalid('nom') }"
      >
    
      <div *ngIf="isFieldInvalid('nom')" class="text-red-500">Nom invalide</div>
    </div>

    <div class="mb-6">
      <label class="block text-gray-700 text-sm font-bold mb-2">Prénom</label>
      <input
        class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        formControlName="prenom"
        required
        (input)="validateField('prenom')"
        [ngClass]="{ 'border-red-500': isFieldInvalid('prenom') }"
      >
    
      <div *ngIf="isFieldInvalid('prenom')" class="text-red-500">Prénom invalide</div>
    </div>

    <div class="mb-6">
      <label class="block text-gray-700 text-sm font-bold mb-2">Adresse Email</label>
      <input
        class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        formControlName="email"
        required
        (input)="validateField('email')"
        [ngClass]="{ 'border-red-500': isFieldInvalid('email') }"
      >
      <div *ngIf="isFieldInvalid('email')" class="text-red-500">Email invalide</div>
    </div>

    <div class="mb-6">
      <label class="block text-gray-700 text-sm font-bold mb-2">Num. Téléphone</label>
      <input
        class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        formControlName="tel"
        required
        (input)="validateField('tel')"
        [ngClass]="{ 'border-red-500': isFieldInvalid('tel') }"
      >
      <div *ngIf="isFieldInvalid('tel')" class="text-red-500">Numero téléphone invalide</div>
    </div>

    <div class="mb-6">
      <label class="block text-gray-700 text-sm font-bold mb-2">Genre</label>
      <div class="mt-2">
        <label class="inline-flex items-center">
          <input
            class="form-radio text-indigo-600"
            type="radio"
            name="genre"
            value="homme"
          >
          <span class="ml-2">Homme</span>
        </label>
        <label class="inline-flex items-center ml-6">
          <input
            class="form-radio text-indigo-600"
            type="radio"
            name="genre"
            value="femme"
          >
          <span class="ml-2">Femme</span>
        </label>
      </div>
    </div>

  </form>
  

Composant InformationProfessionnel : information-professionnel.component.html

HTML
<form class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" [formGroup]="form" (ngSubmit)="onNextStep()">
    <h1 class="text-center font-bold text-2xl">Informations Professionnel</h1>
    <div class="mb-6">
      <label class="block text-gray-700 text-sm font-bold mb-2">Formation</label>
      <input
        class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        formControlName="formation"
        required
        (input)="validateField('formation')"
        [ngClass]="{ 'border-red-500': isFieldInvalid('formation') }"
      >
    
      <div *ngIf="isFieldInvalid('formation')" class="text-red-500">Formation invalide</div>
    </div>
    <div class="mb-6">
      <label class="block text-gray-700 text-sm font-bold mb-2">Niveau</label>
      <input
        class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        formControlName="niveau"
        required
        (input)="validateField('niveau')"
        [ngClass]="{ 'border-red-500': isFieldInvalid('niveau') }"
      >
    
      <div *ngIf="isFieldInvalid('niveau')" class="text-red-500">Niveau invalide</div>
    </div>
    <div class="mb-6">
      <label class="block text-gray-700 text-sm font-bold mb-2">Matricule</label>
      <input
        class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        formControlName="mat"
        required
        (input)="validateField('mat')"
        [ngClass]="{ 'border-red-500': isFieldInvalid('mat') }"
      >
    
      <div *ngIf="isFieldInvalid('mat')" class="text-red-500">Matricule invalide</div>
    </div>
    <div class="mb-6">
      <label class="block text-gray-700 text-sm font-bold mb-2">Date d'inscription</label>
      <input
        type="date"
        class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
        formControlName="date"
        required
        (input)="validateField('date')"
        [ngClass]="{ 'border-red-500': isFieldInvalid('date') }"
      >
    
      <div *ngIf="isFieldInvalid('date')" class="text-red-500">Date d'inscription invalide</div>
    </div>

  </form>
  

Composant Resume : resume.component.html

HTML
<form class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" [formGroup]="form" (ngSubmit)="onSubmitForm()">
    <h1 class="text-center font-bold text-2xl">Description</h1>
    <label class="block text-gray-700 text-sm font-bold mb-2">Description</label>
    <textarea
      class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline h-80"
      formControlName="resume"
      required
      (input)="validateField('resume')"
      [ngClass]="{ 'border-red-500': isFieldInvalid('resume') }"
    ></textarea>
  
    <div *ngIf="isFieldInvalid('resume')" class="text-red-500">Description invalide</div>

  </form>
  

Composant InformationPersonnel : information-personnel.component.ts

JavaScript
import { Component, EventEmitter, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-information-personnel',
  templateUrl: './information-personnel.component.html',
  styleUrls: ['./information-personnel.component.css'],
})
export class InformationPersonnelComponent {
  @Output() nextStep = new EventEmitter<void>();

  form = this.fb.group({
    nom: ['', [Validators.required, Validators.pattern(/^[A-Za-z]+$/)]],
    prenom: ['', [Validators.required, Validators.pattern(/^[A-Za-z]+$/)]],
    email: ['', [Validators.required, Validators.email,]],
    tel: ['', [Validators.required, Validators.minLength(9)]],
  });

  constructor(private fb: FormBuilder) {}

  onNextStep() {
    if (this.form.valid) {
      this.nextStep.emit();
    }
  }
  validateField(field: string) {
    const control = this.form.get(field);
    if (control && control.dirty) {
      control.updateValueAndValidity();
    }
  }
  
  isFieldInvalid(field: string) {
    const control = this.form.get(field);
    return control && control.invalid && (control.dirty || control.touched);
  }
}

Composant InformationProfessionnel : information-professionnel.component.ts

JavaScript
import { Component, EventEmitter, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-information-professionnel',
  templateUrl: './information-professionnel.component.html',
  styleUrls: ['./information-professionnel.component.css'],
})
export class InformationProfessionnelComponent {
  @Output() nextStep = new EventEmitter<void>();

  form = this.fb.group({
    formation: ['', [Validators.required]],
    niveau: ['', [Validators.required]],
    mat: ['', [Validators.required]],
    date: ['', [Validators.required]],
  });

  constructor(private fb: FormBuilder) {}

  validateField(field: string) {
    const control = this.form.get(field);
    if (control && control.dirty) {
      control.updateValueAndValidity();
    }
  }
  
  isFieldInvalid(field: string) {
    const control = this.form.get(field);
    return control && control.invalid && (control.dirty || control.touched);
  }

  onNextStep() {
    if (this.form.valid) {
      this.nextStep.emit();
    }
  }
}

Composant Resume : resume.component.ts

JavaScript
import { Component, EventEmitter, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-resume',
  templateUrl: './resume.component.html',
  styleUrls: ['./resume.component.css'],
})
export class ResumeComponent {
  @Output() submitForm = new EventEmitter<void>();

  form = this.fb.group({
    resume: ['', [Validators.required]],
  });

  constructor(private fb: FormBuilder) {}

  validateField(field: string) {
    const control = this.form.get(field);
    if (control && control.dirty) {
      control.updateValueAndValidity();
    }
  }
  
  isFieldInvalid(field: string) {
    const control = this.form.get(field);
    return control && control.invalid && (control.dirty || control.touched);
  }

  onSubmitForm() {
    if (this.form.valid) {
      this.submitForm.emit();
    }
  }
}

Le composant global AppComponent qui englobe les étapes du formulaire progressif. Ce composant affiche dynamiquement les composants enfants en fonction de l’étape actuelle du formulaire :

Composant Global : formulaire-progressif.component.ts

JavaScript
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css'],
})
export class AppComponent {
  currentStep = 'information-personnel';

  nextStep() {
    if (this.currentStep === 'information-personnel') {
      this.currentStep = 'information-professionnel';
    } else if (this.currentStep === 'information-professionnel') {
      this.currentStep = 'resume';
    }
    // Vous pouvez ajouter davantage de logique de navigation ici
  }

  prevStep() {
    if (this.currentStep === 'information-professionnel') {
      this.currentStep = 'information-personnel';
    } else if (this.currentStep === 'resume') {
      this.currentStep = 'information-professionnel';
    }
    // Vous pouvez ajouter davantage de logique de navigation ici
  }

  submitForm() {
    // Soumettez les données finales ou effectuez d'autres actions
  }
}

Composant Global : formulaire-progressif.component.html

HTML
<!-- formulaire-progressif.component.html -->
<div class="container mx-auto p-4">
  <h2 class="text-2xl font-bold mb-4">Formulaire Progressif</h2>

  <!-- Affichez le composant en fonction de l'étape actuelle -->
  <ng-container [ngSwitch]="currentStep">
    <app-information-personnel
      *ngSwitchCase="'information-personnel'"
      (nextStep)="nextStep()"
    ></app-information-personnel>

    <app-information-professionnel
      *ngSwitchCase="'information-professionnel'"
      (nextStep)="nextStep()"
    ></app-information-professionnel>

    <app-resume *ngSwitchCase="'resume'" (submitForm)="submitForm()"></app-resume>
  </ng-container>

  <!-- Boutons de navigation -->
  <div class="mt-4">
    <button
      (click)="prevStep()"
      [disabled]="currentStep === 'information-personnel'"
      class="bg-gray-300 hover:bg-gray-400 text-gray-700 font-bold py-2 px-4 rounded-l"
    >
      Précédent
    </button>
    <button
      (click)="nextStep()"
      [disabled]="currentStep === 'resume'"
      class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-r"
    >
      Suivant
    </button>
  </div>
</div>

Ce code HTML utilise la directive ngSwitch pour afficher dynamiquement le composant correspondant à l’étape actuelle (currentStep). Les composants enfants, tels que app-information-personnel, app-information-professionnel, et app-resume, émettent des événements pour gérer la navigation entre les étapes. Les boutons « Précédent » et « Suivant » sont désactivés en fonction de l’étape actuelle pour empêcher la navigation incorrecte.

Étape 6 : Tester votre application

Bash
ng serve --start

Ce tutoriel fournit une base pour créer un formulaire progressif avec validation avancée en utilisant Angular et Tailwind CSS. Vous pouvez personnaliser davantage votre formulaire en fonction de vos besoins spécifiques et ajouter des fonctionnalités telles que la gestion des erreurs, la confirmation, etc…

769 commentaires