Afficher un spinner lors d'un chargement avec angular

Dans mon site perso développé avec angular coté front, j'ai choisi d'afficher mes projets sur github et gilab en passant par leurs API respectives.
Ça me permet d'avoir une chouette représentation de mon travail, et c'est actualisé en direct, pas besoin d'aller mettre le site a jour quand je démarre un nouveau projet.

Seulement voilà, comme on appel une API exterieure, on ne peut pas garantir le temps de réponse. Et le temps que tout ceci charge, il va falloir prévenir visuellement mon utilisateur que son contenu est en train de charger.

Pour cela, j'ai opté pour l'affichage d'un spinner. Nous allons donc voir comment créer un composant qu'on pourra réutiliser lors de mes différents chargements de données.

Signaler le chargement

Tout d'abord, il faut identifier que nous sommes en train de charger quelque chose. Dans mon cas, le résultat de mon appel va venir valoriser une variable.

Voici le code du composant qui a besoin de ces données :

personalProjects: Project[];

constructor(private gitDAO : GitDaoService) { }

ngOnInit() {
  this.gitDAO.getGithubProjects().subscribe(
    data => this.personalProjects = data.personalProjects
  )
}

Ainsi, tant que ma variable personalProjects n'est pas valorisée, je sais que ma donnée n'est pas initialisée.

Afficher un message

Maintenant, je vais afficher coté client un message tant que ma variable n'est pas initialisée. Pour cela je rajoute dans le template

<p *ngIf="!personalProjects">
  Loading...
</p>

Désormais, tant que la variable personalProjects n'est pas valorisée, le message loading... s'affiche, puis il disparait au moment ou mes données sont chargées.

Le spinner

Parfait, maintenant qu'on peut afficher le html que l'on veut lors du chargement, on a plus qu'a y afficher un spinner au lieu d'un texte.

Personnellement, j'utilise ici bootstrap pour afficher mon spinner, mais il est possible d'utiliser ce que l'on veut (un gif, une image, un mini-jeu en js...). Des exemples de spinner en css peuvent être trouvé un peu partout, j'apprécie en particulier Load Awesome.

Voici donc comment j'affiche mon spinner :

<div *ngIf="!personalProjects" class="spinner-border" role="status">
  <span class="sr-only">Loading...</span>
</div>

Et voici le résultat :

Transformation en composant

Maintenant qu'on a un spinner, on aimerait pouvoir le réutiliser ailleurs, en le transformant en composant.

Tout d'abord générons notre composant :

ng generate component spinner

on va ensuite ajouter un input a notre composant qui correspondra a la variable qu'on est en train de charger. Ainsi on pourra indiquer dans notre template la variable que notre spinner attends.

import {Component, Input} from '@angular/core';

@Component({
  selector: 'spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.sass']
})
export class SpinnerComponent {

  @Input()
  waitingFor: any;

}

Créons notre template en reprenant ce que nous avons déjà fait :

<div *ngIf="!waitingFor" class="spinner-border" role="status">
  <span class="sr-only">Loading...</span>
</div>

Et voilà, notre composant est terminé.

Maintenant je met a jour le template de mon composant qui a besoin du spinner ainsi :

<spinner [waitingFor]="personalProjects"></spinner>

Et on est bon. Notre spinner va bien s'afficher tant que la variable n'est pas initialisée, et disparaitre une fois cette dernière chargée.

Pour aller plus loin

Le composant que nous avons fait répond à notre problématique de base, mais il n'est pas exempt de défaut. Le principal soucis est qu'en cas d'erreur dans notre service, notre variable n'est pas valorisée, et donc notre spinner continuera de tourner at vitam eternam. Il faut donc prévoir un traitement dans ce cas.

Egalement, notre spinner ne peut surveiller qu'un seul champ en l'état, il faudrait qu'il puisse en surveiller plusieurs si le besoin se présente, en ayant un tableau dans norte composant par exemple.