Angular 18 : Créer un Tableau Bootstrap avec Headers Fixes

Introduction

Dans cet article, nous allons explorer la mise en place d’une vue contenant un tableau interactif composé de plusieurs colonnes et lignes. Ce tableau servira à illustrer une bonne pratique en matière d’interface utilisateur (UI) et d’expérience utilisateur (UX), en mettant l’accent sur l’intuitivité et la simplicité d’utilisation. L’objectif est de garantir une navigation fluide et un rendu visuel agréable, permettant à l’utilisateur d’interagir facilement avec les données présentées.

En plus du tableau, des boutons d’action seront intégrés pour effectuer des mappings entre les données du tableau et une base de données. Bien que les appels API pour ces interactions soient simulés dans ce projet, l’accent est mis sur l’aspect UI/UX, avec un design épuré, des interactions fluides et une expérience utilisateur optimale.

Prérequis

Avant de commencer, assurez-vous de disposer des éléments suivants :

  • Node.js version 18 ou supérieure pour la compatibilité avec Angular 18.
  • Yarn comme gestionnaire de paquets.
  • Connaissances de base en Angular.
  • Une bonne compréhension de Bootstrap pour concevoir une interface utilisateur responsive.

Use Case

Imaginons un cas d’utilisation où vous devez afficher un grand nombre de données issues d’un fichier Excel dans un tableau Bootstrap. Chaque ligne représente un produit et chaque colonne des attributs spécifiques de ces produits (comme l’ID, le nom, la description, etc.).

Le tableau doit permettre à l’utilisateur de visualiser les données facilement et d’effectuer un mapping entre certaines colonnes et les champs de la base de données via un bouton d’action associé à chaque ligne. Ce mapping sera effectué à l’aide de menus déroulants et d’actions simulées, permettant une expérience utilisateur cohérente et intuitive. Le but ici n’est pas de mettre en place une logique serveur complète, mais plutôt de montrer comment concevoir une interface fluide qui permet à l’utilisateur d’interagir efficacement avec un tableau de données.

Grâce à cette mise en place, nous chercherons à optimiser le confort d’utilisation du tableau et la clarté des actions disponibles pour l’utilisateur.

Installation du Projet Angular

Installer Angular CLI

Si Angular CLI n’est pas déjà installé sur votre machine, vous pouvez l’installer en utilisant Yarn avec la commande suivante :

yarn global add @angular/cli@18

Créer un Nouveau Projet Angular

Une fois Angular CLI installé, créez un nouveau projet en exécutant la commande suivante dans votre terminal :

ng new angular-table-example --style=scss --routing --ssr

angular-table-example est le nom de notre projet. Vous pouvez choisir un autre nom.

Lancer le Serveur de Développement

Entrez dans le répertoire de votre projet et lancez le serveur de développement avec les commandes suivantes :

cd angular-table-example
ng serve

Votre projet Angular est maintenant disponible à l’adresse http://localhost:4200. Vous pouvez accéder à cette URL dans votre navigateur pour voir la page d’accueil par défaut d’Angular.

Installation des Modules

Installer Bootstrap

Nous allons utiliser Bootstrap pour styliser notre tableau et nos boutons. Installez Bootstrap avec la commande suivante :

yarn add bootstrap

Ensuite, ouvrez le fichier angular.json et ajoutez le chemin vers les fichiers CSS de Bootstrap dans la section projects > angular-table-example > architect > styles :

"styles": [
  "src/styles.css",
  "node_modules/bootstrap/dist/css/bootstrap.min.css"
]

Cela permettra de charger les styles de Bootstrap dans votre projet Angular.

Configurer un Spinner

Nous allons utiliser un package d’icônes comme Font Awesome pour styliser facilement les boutons et loaders. Pour l’installer :

yarn add @fortawesome/fontawesome-free

Ajoutez également le chemin vers le fichier CSS de Font Awesome dans angular.json :

"styles": [
  "src/styles.css",
  "node_modules/@fortawesome/fontawesome-free/css/all.min.css"
]

Installer ngx-toastr

Nous allons pouvez utiliser une bibliothèque comme ngx-toastr pour afficher des messages de toast plus élégants et non intrusifs. Exécutez la commande suivante pour installer la bibliothèque :

yarn add ngx-toastr
yarn add @angular/animations

Ajoutez le chemin vers le fichier CSS de ngx-toastr dans angular.json :

"styles": [
  "src/styles.css",
  "node_modules/ngx-toastr/toastr.css"
]

Ajoutez les modules nécessaires dans votre application. Ouvrez votre fichier app.config.ts et configurez-le comme suit au niveau des providers :

providers: [
    //...,
    provideToastr({
      timeOut: 2000,
      positionClass: 'toast-top-right',
      preventDuplicates: true,
    }), 
    provideAnimations()
  ]

Création du Composant

Maintenant que notre projet est configuré, nous allons créer un composant Angular pour afficher et gérer les données dans un tableau.

Créez un nouveau composant en utilisant la commande suivante dans le terminal :

ng generate component DataManagement

Cela créera automatiquement les fichiers nécessaires : un fichier TypeScript (data-management.component.ts), un fichier HTML (data-management.component.html), et un fichier CSS/SCSS (data-management.component.scss).

Ajouter la Logique

Dans le fichier data-management.component.ts, ajoutez la logique suivante pour simuler un appel à une API et gérer les actions utilisateur :

import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { ToastrService } from 'ngx-toastr';

@Component({
  selector: 'app-data-management',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './data-management.component.html',
  styleUrl: './data-management.component.scss'
})
export class DataManagementComponent implements OnInit {
  dataPreview: any[] = [];
  columns: any[] = [];
  isDeleting: boolean = false;
  isTesting: boolean = false;
  isImporting: boolean = false;
  mappingList: { label: string; value: string }[] = [];

  constructor(private toastr: ToastrService) { }

  ngOnInit() {
    this.loadData();
    this.initializeMappingList(); 
  }

  loadData() {
    setTimeout(() => {
      this.dataPreview = this.generateData(30);
      this.columns = this.getColumns();
    }, 2000);
  }

  initializeMappingList() {
    this.mappingList = [
      { label: 'Field A', value: 'FieldA' },
      { label: 'Field B', value: 'FieldB' },
      { label: 'Field C', value: 'FieldC' },
      { label: 'Field D', value: 'FieldD' }
    ];
  }

  generateData(count: number) {
    const data = [];
    for (let i = 0; i < count; i++) {
      data.push({
        id: i + 1,
        label: `Label-${i + 1}`,
        company: `Company-${i + 1}`,
        product: `Product-${i + 1}`,
        description: `Description for Product-${i + 1}`,
        status: i % 2 === 0 ? 'Active' : 'Inactive',
        price: (Math.random() * 100).toFixed(2)
      });
    }
    return data;
  }

  getColumns() {
    return [
      { id: 'label', label: 'label' },
      { id: 'company', label: 'Company Name' },
      { id: 'product', label: 'Product Name' },
      { id: 'description', label: 'Extremely long Product Description' },
      { id: 'status', label: 'Status' },
      { id: 'price', label: 'Price ($)' }
    ];
  }

  deleteData() {
    this.isDeleting = true;
    setTimeout(() => {
      this.isDeleting = false;
      this.toastr.success('Action successfully deleted.');
    }, 1500);
  }

  importTest() {
    this.isTesting = true;
    setTimeout(() => {
      this.isTesting = false;
      this.toastr.success('Test successfully completed.'); 
    }, 1500);
  }

  importData() {
    this.isImporting = true;
    setTimeout(() => {
      this.isImporting = false;
      this.toastr.success('Data successfully imported.');
    }, 1500);
  }
  
}

Dans cette logique, nous avons :

  • dataPreview : Contiendra les données fictives.
  • columns : Définira les colonnes du tableau.
  • Trois états de chargement (isDeleting, isTesting, isImporting) : Pour gérer les spinners lors des actions utilisateur.

Création du Tableau

Dans le fichier data-management.component.html, affichez les données sous forme de tableau avec un header fixe et ajoutez les boutons pour la suppression et la mise à jour :

<div class="container pt-5">
    <div class="table-actions">
        <div class="action-buttons">
            <button class="btn btn-secondary">
                <i class="fas fa-chevron-left"></i>
                Back
            </button>
            <button class="btn btn-danger" (click)="deleteData()" [disabled]="isDeleting">
                <i class="fas fa-ban" *ngIf="!isDeleting"></i>
                <div class="spinner-grow text-light sizespinner" *ngIf="isDeleting" role="status">
                    <span class="sr-only">Loading...</span>
                </div>
                Delete
            </button>
        </div>

        <div class="action-buttons">
            <button class="btn btn-warning" (click)="importTest()" [disabled]="isTesting">
                <i class="fas fa-vial" *ngIf="!isTesting"></i>
                <div class="spinner-grow text-light sizespinner" *ngIf="isTesting" role="status">
                    <span class="sr-only">Loading...</span>
                </div>
                Test
            </button>
            <button class="btn btn-success" (click)="importData()" [disabled]="isImporting">
                <i class="fas fa-file-import" *ngIf="!isImporting"></i>
                <div class="spinner-grow text-light sizespinner" *ngIf="isImporting" role="status">
                    <span class="sr-only">Loading...</span>
                </div>
                Import
            </button>
        </div>
    </div>

    <div class="table-container">
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th class="number">#</th>
                    <th *ngFor="let column of columns">
                        {{ column.label }}
                        <select class="form-control">
                            <option value="">-- No selection --</option>
                            <option *ngFor="let product of mappingList" [value]="product.label">
                                {{ product.label }}
                            </option>
                        </select>
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr *ngFor="let row of dataPreview">
                    <td class="number">{{ row.id }}</td>
                    <td *ngFor="let column of columns">{{ row[column.id] }}</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>

Les boutons déclenchent les actions et les spinners sont affichés lorsque des processus sont en cours (suppression, simulation, mise à jour).

Stylisation du Tableau avec un Header Fixe

Ajoutez le style suivant dans le fichier data-management.component.scss pour fixer le header ainsi que la première colonne du tableau et styliser les spinners :

/* Section for buttons */
.table-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
}

/* Container for buttons on the right */
.action-buttons {
    display: flex;
    gap: 10px;
}

/* Buttons */
button.btn {
    padding: 8px 16px;
    font-size: 14px;
}

/* Fix equal width for all table columns */
table {
    table-layout: fixed;
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
}

th,
td {
    width: 150px;
    text-align: center;
    padding: 8px;
    border: 1px solid lightgrey;
}

select.form-control {
    width: 100%;
}

/* Styles for the header */
thead th {
    background-color: #f8f9fa;
    font-weight: bold;
    position: sticky;
    top: 0;
    /* To keep the header above the content */
    box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.4);
    /* A separation effect */
    z-index: 10;
}

/* Fix the first column */
td.number {
    position: sticky;
    left: 0;
    background-color: #f8f9fa;
    /* White background for the fixed column */
    z-index: 5;
}

/* Handle longer headers */
thead th select {
    font-size: 14px;
    width: 100%;
    min-height: 36px;
}

.number {
    width: 50px;
}

.table-container {
    overflow-x: auto;
    max-height: 80vh;
    /* Limit table height to allow vertical scrolling */
}

/* Apply a fixed style for the header */
.table-container thead th {
    position: sticky;
    top: 0;
    z-index: 10;
    margin-top: -5px;
}

.sizespinner {
    size: 1rem;
}

.spinner-grow {
    display: inline-block;
    width: 1rem !important;
    height: 1rem !important;
}

Intégration dans le Composant App

Une fois que vous avez suivi toutes les étapes ci-dessus et que votre serveur de développement est en cours d’exécution, vous pouvez ajouter votre composant dans le fichier app.component.ts et app.component.html pour rendre votre nouveau composant visible :

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { DataManagementComponent } from './data-management/data-management.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, DataManagementComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {
  title = 'angular-table-example';
}
<app-data-management></app-data-management>

Tests

Ouvrez votre navigateur et naviguez à l’adresse http://localhost:4200.

Conclusion

En conclusion, la mise en place d’un tableau interactif dans Angular, enrichi de fonctionnalités UI/UX telles que les boutons d’action, les spinners et la gestion des mappings, illustre l’importance d’une interface utilisateur fluide et intuitive. L’intégration de Bootstrap, Font Awesome, et ngx-toastr renforce l’esthétique et la réactivité de l’application, tout en simplifiant l’expérience utilisateur. En suivant ces bonnes pratiques, vous pouvez créer des interfaces efficaces et ergonomiques, capables de gérer de grands ensembles de données tout en offrant une expérience utilisateur optimale.

Architecte logiciel & CTO * Plus de publications

Architecte logiciel, Développeur d'application diplomé d'ETNA, la filière d'alternance d'Epitech, j'ai acquis une expertise solide dans le développement d'applications, travaillant sur des projets complexes et techniquement diversifiés. Mon expérience englobe l'utilisation de divers frameworks et langages, notamment Symfony, Api Platform, Drupal, Zend, React Native, Angular, Vue.js, Shell, Pro*C...

Contributeurs

0 0 votes
Évaluation de l'article
guest
0 Commentaires
Le plus ancien
Le plus récent Le plus populaire
Commentaires en ligne
Afficher tous les commentaires

Ingénierie informatique (SSII)

Applize crée des logiciels métiers pour accompagner les entreprises dans la transition vers le zéro papier.


Avez-vous un projet en tête ? Discutons-en.