130 votes

Affichage d'un écran de chargement lors de la navigation entre les routes en Angular 2

Comment afficher un écran de chargement lorsque je modifie une route en Angular 2 ?

0 votes

5 votes

La question liée ci-dessus (le commentaire d'AndrewL64) concerne AngularJS, et non "Angular" (2+).

206voto

borislemke Points 366

Le routeur Angular actuel fournit des événements de navigation. Vous pouvez vous abonner à ces événements et modifier l'interface utilisateur en conséquence. N'oubliez pas de tenir compte d'autres événements tels que NavigationCancel et NavigationError pour arrêter la toupie en cas d'échec des transitions de routeur.

app.component.ts - votre composant racine

...
import {
  Router,
  // import as RouterEvent to avoid confusion with the DOM Event
  Event as RouterEvent,
  NavigationStart,
  NavigationEnd,
  NavigationCancel,
  NavigationError
} from '@angular/router'

@Component({})
export class AppComponent {

  // Sets initial value to true to show loading spinner on first load
  loading = true

  constructor(private router: Router) {
    router.events.subscribe((event: RouterEvent) => {
      this.navigationInterceptor(event)
    })
  }

  // Shows and hides the loading spinner during RouterEvent changes
  navigationInterceptor(event: RouterEvent): void {
    if (event instanceof NavigationStart) {
      this.loading = true
    }
    if (event instanceof NavigationEnd) {
      this.loading = false
    }

    // Set loading state to false in both of the below events to hide the spinner in case a request fails
    if (event instanceof NavigationCancel) {
      this.loading = false
    }
    if (event instanceof NavigationError) {
      this.loading = false
    }
  }
}

app.component.html - votre vue Racine

<div class="loading-overlay" *ngIf="loading">
    <!-- show something fancy here, here with Angular 2 Material's loading bar or circle -->
    <md-progress-bar mode="indeterminate"></md-progress-bar>
</div>

Amélioration des performances Réponse : Si vous vous souciez des performances, il existe une meilleure méthode, elle est légèrement plus fastidieuse à mettre en œuvre mais l'amélioration des performances vaudra le travail supplémentaire. Au lieu d'utiliser *ngIf pour afficher conditionnellement le compteur rotatif, nous pourrions tirer parti de l'outil Angular NgZone et Renderer pour activer/désactiver le spinner, ce qui permet de contourner la détection de changement d'Angular lorsque nous changeons l'état du spinner. J'ai trouvé que cela rendait l'animation plus fluide par rapport à l'utilisation de la fonction *ngIf ou un async tuyau.

C'est similaire à ma réponse précédente avec quelques modifications :

app.component.ts - votre composant racine

...
import {
  Router,
  // import as RouterEvent to avoid confusion with the DOM Event
  Event as RouterEvent,
  NavigationStart,
  NavigationEnd,
  NavigationCancel,
  NavigationError
} from '@angular/router'
import {NgZone, Renderer, ElementRef, ViewChild} from '@angular/core'

@Component({})
export class AppComponent {

  // Instead of holding a boolean value for whether the spinner
  // should show or not, we store a reference to the spinner element,
  // see template snippet below this script
  @ViewChild('spinnerElement')
  spinnerElement: ElementRef

  constructor(private router: Router,
              private ngZone: NgZone,
              private renderer: Renderer) {
    router.events.subscribe((event: RouterEvent) => {
      this._navigationInterceptor(event)
    })
  }

  // Shows and hides the loading spinner during RouterEvent changes
  private _navigationInterceptor(event: RouterEvent): void {
    if (event instanceof NavigationStart) {
      // We wanna run this function outside of Angular's zone to
      // bypass change detection
      this.ngZone.runOutsideAngular(() => {
        // For simplicity we are going to turn opacity on / off
        // you could add/remove a class for more advanced styling
        // and enter/leave animation of the spinner
        this.renderer.setElementStyle(
          this.spinnerElement.nativeElement,
          'opacity',
          '1'
        )
      })
    }
    if (event instanceof NavigationEnd) {
      this._hideSpinner()
    }
    // Set loading state to false in both of the below events to
    // hide the spinner in case a request fails
    if (event instanceof NavigationCancel) {
      this._hideSpinner()
    }
    if (event instanceof NavigationError) {
      this._hideSpinner()
    }
  }

  private _hideSpinner(): void {
    // We wanna run this function outside of Angular's zone to
    // bypass change detection,
    this.ngZone.runOutsideAngular(() => {
      // For simplicity we are going to turn opacity on / off
      // you could add/remove a class for more advanced styling
      // and enter/leave animation of the spinner
      this.renderer.setElementStyle(
        this.spinnerElement.nativeElement,
        'opacity',
        '0'
      )
    })
  }
}

app.component.html - votre vue Racine

<div class="loading-overlay" #spinnerElement style="opacity: 0;">
    <!-- md-spinner is short for <md-progress-circle mode="indeterminate"></md-progress-circle> -->
    <md-spinner></md-spinner>
</div>

1 votes

Super, j'apprécie votre approche. Cela m'est déjà arrivé et j'ai remplacé ma longue méthode par cette idée séduisante, puis j'ai dû revenir en arrière parce que j'ai perdu le contrôle de la méthode. router navigation y it triggering the spinner et ne pas être capable de l'arrêter. navigationInterceptor semble être une solution mais je ne suis pas sûr que quelque chose d'autre attende de se briser. Si on le mélange avec le async requests ça va réintroduire le problème, je pense.

0 votes

@A_Singh, avez-vous défini le loading l'état de false sur NavigationCancel y NavigationError également ? Vous voudriez définir la variable à false dans les deux autres conditions au cas où une requête HTTP échouerait (en cas de 404s etc.) A propos, j'ai utilisé cette approche dans de nombreux projets différents dans notre société sans aucun problème. Vous pouvez également utiliser la fonction navigationInterceptor dans les composants si vous souhaitez définir manuellement les pages qui ont besoin de l'indicateur de chargement et celles qui n'en ont pas besoin.

0 votes

@borislemke Je l'ai fait en utilisant l'option old-Router . Je n'ai pas essayé le nouveau. Je pense que votre last edit résoudra la plupart des problèmes que je suppose :)

40voto

Ankit Singh Points 15545

UPDATE:3 Maintenant que je suis passé à un nouveau routeur, @borislemke ne fonctionnera pas si vous utilisez CanDeactivate garde. Je reviens à mon ancienne méthode, ie: cette réponse

UPDATE2 : Les événements de routeur dans new-router semblent prometteurs et la réponse par @borislemke semble couvrir les principaux aspects de l'implémentation des spinners, je ne l'ai pas testé mais je le recommande.

UPDATE1 : J'ai écrit cette réponse à l'époque de Old-Router alors qu'il n'y avait qu'un seul événement route-changed notifié par router.subscribe() . J'ai également ressenti une surcharge de l'approche ci-dessous et j'ai essayé de le faire en utilisant seulement router.subscribe() et il backfired parce qu'il n'y avait aucun moyen de détecter canceled navigation . J'ai donc dû revenir à l'approche longue (double travail).


Si vous vous y connaissez en Angular2, voici ce dont vous aurez besoin


Boot.ts

import {bootstrap} from '@angular/platform-browser-dynamic';
import {MyApp} from 'path/to/MyApp-Component';
import { SpinnerService} from 'path/to/spinner-service';

bootstrap(MyApp, [SpinnerService]);

Composant racine - (MyApp)

import { Component } from '@angular/core';
import { SpinnerComponent} from 'path/to/spinner-component';
@Component({
  selector: 'my-app',
  directives: [SpinnerComponent],
  template: `
     <spinner-component></spinner-component>
     <router-outlet></router-outlet>
   `
})
export class MyApp { }

Composant de la Spinner (s'abonner au service Spinner pour changer la valeur de active en conséquence)

import {Component} from '@angular/core';
import { SpinnerService} from 'path/to/spinner-service';
@Component({
  selector: 'spinner-component',
  'template': '<div *ngIf="active" class="spinner loading"></div>'
})
export class SpinnerComponent {
  public active: boolean;

  public constructor(spinner: SpinnerService) {
    spinner.status.subscribe((status: boolean) => {
      this.active = status;
    });
  }
}

Spinner-Service (lancer ce service)

Définir une observable à laquelle le composant spinner doit souscrire pour changer le statut lors d'un changement, et une fonction pour connaître et définir le spinner actif/inactif.

import {Injectable} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import 'rxjs/add/operator/share';

@Injectable()
export class SpinnerService {
  public status: Subject<boolean> = new Subject();
  private _active: boolean = false;

  public get active(): boolean {
    return this._active;
  }

  public set active(v: boolean) {
    this._active = v;
    this.status.next(v);
  }

  public start(): void {
    this.active = true;
  }

  public stop(): void {
    this.active = false;
  }
}

Tous les composants des autres routes

(échantillon) :

import { Component} from '@angular/core';
import { SpinnerService} from 'path/to/spinner-service';
@Component({
   template: `<div *ngIf="!spinner.active" id="container">Nothing is Loading Now</div>`
})
export class SampleComponent {

  constructor(public spinner: SpinnerService){} 

  ngOnInit(){
    this.spinner.stop(); // or do it on some other event eg: when xmlhttp request completes loading data for the component
  }

  ngOnDestroy(){
    this.spinner.start();
  }
}

0 votes

voir ceci également. Je ne sais pas combien d'animations Angular supporte par défaut.

0 votes

Pouvez-vous m'aider à rédiger le service pour le spinner ? Je peux faire les animations et autres en CSS moi-même, mais ce serait bien si vous pouviez m'aider avec le service.

0 votes

voir cette implémentation aussi, même chose mais pas exactement

10voto

Milad Points 12206

Pourquoi ne pas utiliser un simple css ?

<router-outlet></router-outlet>
<div class="loading"></div>

Et dans vos styles :

div.loading{
    height: 100px;
    background-color: red;
    display: none;
}
router-outlet + div.loading{
    display: block;
}

Ou même nous pouvons le faire pour la première réponse :

<router-outlet></router-outlet>
<spinner-component></spinner-component>

Et puis simplement

spinner-component{
   display:none;
}
router-outlet + spinner-component{
    display: block;
}

L'astuce ici est que les nouvelles routes et les nouveaux composants apparaîtront toujours après l'entrée en vigueur de la loi. routeur-sortie Ainsi, avec un simple sélecteur css, nous pouvons afficher et masquer le chargement.

0 votes

<router-outlet> ne nous permet pas de passer la valeur du composant au composant parent, donc il sera un peu complexe de cacher la div de chargement.

2 votes

En outre, il peut être très ennuyeux, si votre application a beaucoup de changements de route, de voir le spinner à chaque fois instantanément. Il est préférable d'utiliser RxJs et de définir une temporisation pour qu'elle n'apparaisse qu'après un court délai.

2voto

Simon_Weaver Points 31141

Si vous avez une logique spéciale requise pour le premièrement seulement, vous pouvez faire ce qui suit :

AppComponent

    loaded = false;

    constructor(private router: Router....) {
       router.events.pipe(filter(e => e instanceof NavigationEnd), take(1))
                    .subscribe((e) => {
                       this.loaded = true;
                       alert('loaded - this fires only once');
                   });

J'en avais besoin pour masquer mon pied de page, qui apparaissait autrement en haut de la page. De même, si vous ne voulez un chargeur que pour la première page, vous pouvez utiliser ceci.

1voto

walkerbox Points 745

Vous pouvez également utiliser ceci solution existante . La démo est ici . Il ressemble à la barre de chargement de Youtube. Je viens de le trouver et de l'ajouter à mon propre projet.

0 votes

Cela aide-t-il les résolveurs ? Mon problème est que pendant que les résolveurs ramènent les données, je suis incapable de montrer le spinner n'importe où parce que le composant cible réel ngOninit n'a pas encore été appelé ! !! Mon idée était d'afficher le spinner dans ngOnInit et de le cacher une fois que les données résolues sont retournées par la souscription à la route.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X