Maîtrise du glisser-déposer dans les tableaux Angular avec ngx-datatable

Optimisation des interactions de tableau dans Angular avec ngx-datatable

Dans les applications riches en données, les utilisateurs s'attendent à pouvoir personnaliser leur espace de travail, notamment en réorganisant les colonnes ou en manipulant les lignes via le glisser-déposer. Le composant ngx-datatable offre une architecture robuste pour répondre à ces exigences d'interface utilisateur, transformant des grilles de données statiques en outils interactifs et ergonomiques.

Approche 1 : Configuration de base pour le réordonnancement des colonnes

Le scénario le plus courant consiste à permettre aux utilisateurs de déplacer les colonnes selon leurs préférences, par exemple pour placer les indicateurs clés en début de tableau. L'activation de cette fonctionnalité nécessite l'ajustement de quelques propriétés fondamentales du composant.

@Component({
  selector: 'app-basic-grid',
  template: `
    <ngx-datatable
      class="bootstrap"
      [rows]="dataRecords"
      [columns]="gridColumns"
      [reorderable]="isReorderEnabled"
      (reorder)="processColumnShift($event)">
    </ngx-datatable>
  `
})
export class BasicGridComponent {
  isReorderEnabled = true;
  
  gridColumns = [
    { name: 'Nom', prop: 'fullName' },
    { name: 'Âge', prop: 'age' },
    { name: 'Courriel', prop: 'emailAddress' },
    { name: 'Entreprise', prop: 'organization' }
  ];
  
  dataRecords: any[] = []; 

  processColumnShift(shiftEvent: any): void {
    const { prevIndex, newIndex } = shiftEvent;
    const displacedCol = this.gridColumns.splice(prevIndex, 1)[0];
    this.gridColumns.splice(newIndex, 0, displacedCol);
  }
}

Les attributs essentiels à maîtriser incluent reorderable pour activer la fonctionnalité globale, swapColumns pour définir si le déplacement échange les colonnes ou les insère, et draggable qui peut être appliqué au niveau de chaque colonne individuellement.

Approche 2 : Contrôle granulaire et rétroaction visuelle

Certaines colonnes structurelles, comme les identifiants uniques ou les menus d'actions contextuels, doivent rester fixes. Il est crucial de désactiver le glisser-déposer pour ces éléments tout en fournissant des indices visuels clairs pour les colonnes mobiles.

  gridColumns = [
    { name: 'Identifiant', prop: 'id', draggable: false, cellClass: 'locked-col' },
    { name: 'Utilisateur', prop: 'username', draggable: true, cellClass: 'movable-col' },
    { name: 'Rôle', prop: 'role', draggable: true, cellClass: 'movable-col' },
    { name: 'Actions', prop: 'operations', draggable: false, cellClass: 'locked-col' }
  ];

L'expérience utilisateur est ensuite enrichie via des feuilles de style dédiées qui modifient le curseur et l'apparence des en-têtes lors des interactions.

.ngx-datatable .datatable-header-cell.movable-col {
  cursor: grab;
  transition: background-color 0.2s ease;
}

.ngx-datatable .datatable-header-cell.movable-col:active {
  cursor: grabbing;
  background: #e9ecef;
}

.ngx-datatable .datatable-header-cell.locked-col {
  cursor: not-allowed;
  opacity: 0.7;
  user-select: none;
}

Approche 3 : Gestion d'état avancée et persistance du layout

Pour les applications d'entreprise, il est souvent nécessaire de suivre l'état du glisser-déposer en temps réel, d'afficher des superpositions d'interface et de sauvegarder la configuration de l'utilisateur pour ses prochaines visites.

@Component({
  selector: 'app-advanced-grid',
  template: `
    <ngx-datatable
      [rows]="records"
      [columns]="tableColumns"
      [reorderable]="true"
      [swapColumns]="useSwapLogic"
      (reorder)="commitReorder($event)"
      (dragStart)="initiateDrag()"
      (dragEnd)="finalizeDrag()">
    </ngx-datatable>
    <div *ngIf="isDragging" class="ui-drag-overlay">Réorganisation en cours...</div>
  `
})
export class AdvancedGridComponent implements OnInit {
  isDragging = false;
  useSwapLogic = false;
  tableColumns: any[] = [];
  records: any[] = [];
  private readonly CACHE_KEY = 'grid_layout_prefs';

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

  initiateDrag(): void {
    this.isDragging = true;
  }

  finalizeDrag(): void {
    this.isDragging = false;
  }

  commitReorder(event: any): void {
    const { prevIndex, newIndex } = event;
    if (!this.isValidIndex(newIndex)) return;

    this.applyNewOrder(prevIndex, newIndex);
    this.cacheLayout();
  }

  private isValidIndex(idx: number): boolean {
    return idx >= 0 && idx < this.tableColumns.length;
  }

  private applyNewOrder(from: number, to: number): void {
    if (this.useSwapLogic) {
      [this.tableColumns[from], this.tableColumns[to]] = [this.tableColumns[to], this.tableColumns[from]];
    } else {
      const item = this.tableColumns.splice(from, 1)[0];
      this.tableColumns.splice(to, 0, item);
    }
    this.tableColumns = [...this.tableColumns]; 
  }

  private cacheLayout(): void {
    localStorage.setItem(this.CACHE_KEY, JSON.stringify(this.tableColumns));
  }

  private restoreLayout(): void {
    const cached = localStorage.getItem(this.CACHE_KEY);
    if (cached) {
      try {
        this.tableColumns = JSON.parse(cached);
      } catch {
        this.loadDefaultColumns();
      }
    } else {
      this.loadDefaultColumns();
    }
  }

  private loadDefaultColumns(): void {
    this.tableColumns = [
      { name: 'ID', prop: 'id' },
      { name: 'Statut', prop: 'status' }
    ];
  }
}

Optimisations et bonnes pratiques

L'implémentation du glisser-déposer doit être accompagnée de stratégies d'optimisation pour maintenir la fluidité de l'application, surtout lors de la manipulation de grands volumes de données.

  • Défilement virtuel : Activez le rendu virtuel pour les jeux de données massifs afin d'éviter le blocage du thread principle lors des recalculs de mise en page.
  • Détection de changements : Adoptez la stratégie OnPush dans vos composants pour limiter les cycles de vérification d'Angular aux seules mutations explicites du tableau.
  • Minimisation des DOM events : Évitez d'attacher des écouteurs d'événements lourds directement sur les cellules pendant la phase de glissement.

Des utilitaires supplémentaires peuvent être intégrés pour offrir plus de flexibilité à l'utilisateur final, comme la possibilité d'exporter ou de réinitialiser la configuration.

  toggleReorderBehavior(): void {
    this.useSwapLogic = !this.useSwapLogic;
  }

  revertToFactoryDefaults(): void {
    this.loadDefaultColumns();
    this.cacheLayout();
  }

  exportLayoutProfile(): void {
    const profile = {
      layout: this.tableColumns,
      exportedAt: new Date().toISOString()
    };
    const blob = new Blob([JSON.stringify(profile, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'grid-layout.json';
    a.click();
    URL.revokeObjectURL(url);
  }

Étiquettes: Angular ngx-datatable TypeScript drag-and-drop State-Management

Publié le 8 juillet à 22h04