Tuto : créer un tableau de bord paramétrable sur Angular

Le 23/02/2022 Par Romain VIDALjavascriptangularframework

Dans cet article tutoriel, nous allons voir comment créer un tableau de bord sur lequel l'utilisateur va pouvoir choisir les widgets qu'il souhaite afficher. On aura une collection de widgets qu' Angular chargera au besoin.

L'objectif du tuto "Tableau de bord paramétrable Angular"

L'objectif est d'éditer un tableau de bord qui affichera les différents widgets que l'utilisateur aura choisis. Il pourra choisir dans une liste les widget qu'il souhaite afficher. Un bouton "+" lui permettra de les ajouter.

Un service sera responsable de ramener la liste de widgets, qui seront simplement des components Angular.

Le tableau de bord récupérera cette liste et affichera chaque widget. On pourra ajouter ou supprimer un type de widget sans affecter la gestion de l'ensemble

C'est parti pour le tuto Angular !

Étape 1 : créer le tableau de bord

Commençons par créer le tableau de bord qui va afficher les widgets.

Son rôle est uniquement d'afficher la liste des widgets. On la récupère dans l'initialisation du composant et elle sera maintenue à jour par le service WidgetService.

@Component({
  selector: 'app-dashboard',
  template: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
  
  // La liste des définitions des widgets 
  // Plus d'info sur le type WidgetDefinition dans la suite de l'article :)      
  public $widgetList: Observable<WidgetDefinition<unknown>[]>;

  constructor(
    private widgetService: WidgetService
  ) {
  }

  ngOnInit(): void {
    this.$widgetList = this.widgetService.getWidgets();
  }
}

Le HTML est, lui aussi, assez simple.

<h1>dashboard works!</h1>
<div class="widget-container">
  <!-- On parcourt la liste des widgets -->
  <ng-container *ngFor="let widget of $widgetList | async">
    <!-- C'est dans ce component que la magie opère -->
    <app-widget
      [ngClass]="widget.classCss"
      [widget]="widget"
    ></app-widget>
  </ng-container>
</div>

Étape 2 : définir les widgets

On va maintenant avoir besoin de définir nos widgets pour continuer !

Pour afficher les components, on va devoir définir du type de component à créer. On peut y ajouter quelque données pour que les widgets soient paramétrables et pourquoi pas une classe CSS pour pouvoir, par exemple, afficher plusieurs tailles de widget ;)

export class WidgetDefinition<T> {
  classCss: WidgetClass;
  widgetComponent: Type<T>;
  widgetData: any;
}

export enum WidgetClass {
  Size2x2 = 'size2x2',
  Size4x4 = 'size4x4'
}

On utilise le type Type importé avec import {Type} from '@angular/core'; et qui est utilisé par Angular pour représenter un component :

Par exemple Type<WidgetHorlogeComponent>

Étant donné qu'on ne saura pas avec quel component on va travailler, on utilisera le mot clé unknow de TypeScript, qui correspond a un type any mais ne permet pas d'effectuer des opérations sur cette variable.

Une dernière chose est que nos widgets doivent avoir une structure commune pour qu'on puisse les paramétrer, sinon où va-t-on injecter les widgetData ?

On va donc ajouter une dernière interface que les widgets devront implémenter.

export interface WidgetInterface {
  data: string;
}

Étape 3 : créer le WidgetContainerComponent

Ce component sera en quelque sorte un container pour les implémentations de widgets qui seront sélectionnables dans le dashboard.

Encore une fois pour le HTML, restons simple.

Nous allons simplement afficher un titre suivi du widget qui sera injecté dans le template.

<p>Widget</p>
<ng-template appWidgetHost></ng-template>

Pour pouvoir injecter le component on va devoir définir une nouvelle directive pour accéder à la vue du template, ce qui nous permettra de remplacer le template par notre widget.

@Directive({
  selector: '[appWidgetHost]'
})
export class WidgetHostDirective {
  // Ce viewContainerRef nous permettra de remplacer le contenu du template
  constructor(public viewContainerRef: ViewContainerRef) { }
}

On a tout mis en place, il ne nous reste plus qu'à dire à Angular de créer le component passé en paramètre dans notre template !

Les anciennes version d'Angular ( < 13 ) devront utiliser le ComponentFactoryResolver fourni dans angular/core. Comme son nom l'indique, ce service permet d'accéder directement au factory utilisé qu'Angular créé et donc d'instancier nous-même les components.

À partir d'Angular 13, il est permis de créer un component passant simplement son Type au ViewContainer.

  @Input() widget: WidgetDefinition<unknown>;

  // {static: true} permet d'accèder à la vue dans le ngInit
  // Si c'est obligatoire pour l'ajout dynamique de component, c'est en revanche déconseillé dans la 
  // majorité des autres usages    
  @ViewChild(WidgetHostDirective, {static: true}) widgetHost: WidgetHostDirective;
  
  constructor(
      // Pour Angular < 13
      // private componentFactoryResolver: ComponentFactoryResolver
  ) {
  }

  public ngOnInit(): void {
    // Dans les version d'Angular < 13 on doit récupère la factory pour le type de widget
    // const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.widget.widgetComponent);
 
    // L'emplacement où l'on va injecter le widget
    const viewContainerRef = this.widgetHost.viewContainerRef;
    // On vérifie qu'il n'y a rien d'affiché
    viewContainerRef.clear();
    
    // Il ne reste plus qu'à créer le widget et injecter ces données
    // Pour les anciennes versions on injectera la componentFactory à la place du Type du widget
    const componentRef = viewContainerRef.createComponent<any>(this.widget.widgetComponent);
    componentRef.instance.data = this.widget.widgetData;
  }

Étape 4 : définir les widget components

On peut définir deux ou trois widgets. Ce sont des components qui vont implémenter l'interface WidgetInterface

On a défini WidgetHorlogeComponent et de la même façon WidgetMeteoComponent

@Component({
  selector: 'app-widget-horloge',
  template: `<p>Heure : {{data}}</p>`,
  styleUrls: ['./widget-horloge.component.css']
})
export class WidgetHorlogeComponent implements WidgetInterface {
  data: any;
}
@Component({
  selector: 'app-widget-meteo',
  template: `<p>Météo : {{data}}</p>`,
  styleUrls: ['./widget-meteo.component.css']
})
export class WidgetMeteoComponent implements WidgetInterface {
  data: any;
}

Étape 5 : créer le service

Il ne reste plus qu'à créer le service qui pourvoira notre application en widget et le tour sera joué !

Il va simplement maintenir un observable a jour qui contiendra la liste des widgets.

  // Quelques widgets par defaut  
  public widgetList: WidgetDefinition<unknown>[] = [
    {
      classCss: WidgetClass.Size2x2,
      widgetComponent: WidgetMeteoComponent,
      widgetData: 'Nuageux cette après-midi',
    },
    {
      classCss: WidgetClass.Size4x4,
      widgetComponent: WidgetHorlogeComponent,
      widgetData: '04:20',
    }
  ];

  $widgetSubject = new ReplaySubject();

  constructor() {
    // On initialise simplement la liste des widgets
    this.$widgetSubject.next(this.widgetList);
  }

  public getWidgets(): Observable<any> {
    return this.$widgetSubject;
  }

Tuto terminé !

Si vous avez suivi toutes ces étapes, bravo ! Le dashboard est désormais opérationnel et charge correctement les widgets par défaut.

Le plus dur est fait et il ne reste que quelques adaptations pour que les utilisateurs puissent ajouter et supprimer des widgets à leur guise, un peu de CSS pour faire joli, mais pour ça, on vous laisse faire ! ;)

Besoin d'expertise pour votre projet Angular ? Contactez-nous pour qu'on en discute !

Sommaire

  • fleche vers la droite L'objectif du tuto "Tableau de bord paramétrable Angular"
  •         fleche vers la droite Étape 1 : créer le tableau de bord
  •         fleche vers la droite Étape 2 : définir les widgets
  •         fleche vers la droite Étape 3 : créer le WidgetContainerComponent
  •         fleche vers la droite Étape 4 : définir les widget components
  •         fleche vers la droite Étape 5 : créer le service
  • fleche vers la droite Tuto terminé !

À voir aussi

Tous les articles