Afficher l'écran de chargement lors de la navigation entre les routes dans Angular 2

Comment afficher un écran de chargement lorsque je change D'itinéraire dans Angular 2?

95
demandé sur think123 2016-05-06 13:04:43

5 réponses

Le routeur angulaire actuel fournit des événements de Navigation. Vous pouvez vous y abonner et apporter des modifications à L'interface utilisateur en conséquence. N'oubliez pas de compter dans d'autres Événements tels que NavigationCancel et NavigationError pour arrêter votre spinner en cas routeur transitions échouer.

app.composant.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.composant.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>

Performance améliorée réponse: Si vous vous souciez de la performance, il existe une meilleure méthode, elle est légèrement plus fastidieux à mettre en œuvre, mais l'amélioration des performances vaudra le travail supplémentaire. Au lieu d'utiliser *ngIf pour montrer conditionnellement le spinner, nous pourrions tirer parti des NgZone et Renderer D'Angular pour allumer / éteindre le spinner qui contournera la détection de changement D'Angular lorsque nous changeons l'état du spinner. J'ai trouvé cela pour rendre l'animation plus fluide par rapport à l'utilisation de *ngIf ou d'un tuyau async.

Ceci est similaire à ma réponse précédente avec certains tweaks:

app.composant.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.composant.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>
156
répondu borislemke 2017-08-11 04:51:48

Mise à jour: 3 Maintenant que j'ai mis à niveau vers un nouveau routeur, l'approche de @borislemke ne fonctionnera pas si vous utilisez CanDeactivate guard. Je suis dégradant à mon ancienne méthode, ie: cette réponse

UPDATE2 : les événements du routeur dans new-router semblent prometteurs et la réponse par @ borislemke semble couvrir l'aspect principal de l'implémentation du spinner, Je ne l'ai pas testé mais je le recommande.

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


Si vous connaissez votre chemin dans Angular2, c'est ce dont vous aurez besoin


De Démarrage.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 { }

Spinner-Component (s'abonnera à Spinner-service 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 (bootstrap ce service)

Définissez un observable à souscrire par spinner-component pour changer l'état sur le changement, et la 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;
  }
}

Composants De Toutes Les Autres Routes

(exemple):

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();
  }
}
37
répondu Ankit Singh 2018-02-05 19:41:39

Pourquoi ne pas simplement utiliser CSS simple:

<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 tout simplement

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

L'astuce ici est que les nouvelles routes et composants apparaîtront toujours après router-outlet , donc avec un simple sélecteur css, nous pouvons afficher et masquer le chargement.

9
répondu Milad 2016-12-27 00:08:43

Si vous avez une logique spéciale requise pour la première route , Vous pouvez effectuer les opérations suivantes:

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'avais besoin de cela pour cacher mon pied de page, qui apparaissait autrement en haut de la page. Aussi, si vous voulez seulement un chargeur pour la première page, vous pouvez l'utiliser.

1
répondu Simon_Weaver 2018-06-13 08:19:44

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

0
répondu walkerbox 2018-03-04 16:17:20