Comment afficher un écran de chargement lorsque je change un itinéraire dans Angular 2?
Comment afficher un écran de chargement lorsque je change un itinéraire dans Angular 2?
Réponses:
Le routeur angulaire actuel fournit des événements de navigation. Vous pouvez vous y abonner et apporter des modifications à l'interface en conséquence. N'oubliez pas de compter dans d'autres événements tels que NavigationCancel
et NavigationError
d'arrêter votre spinner 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) {
this.router.events.subscribe((e : RouterEvent) => {
this.navigationInterceptor(e);
})
}
// 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>
Réponse améliorée : 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 spinner, nous pourrions tirer parti de Angular NgZone
et Renderer
activer / désactiver le spinner, ce qui contournera la détection de changement d'Angular lorsque nous changerons l'état du spinner. J'ai trouvé cela pour rendre l'animation plus fluide par rapport à l'utilisation *ngIf
ou à unasync
tuyau.
Ceci est similaire à ma réponse précédente avec quelques ajustements:
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(this._navigationInterceptor)
}
// 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>
router navigation
et que je it triggering the spinner
ne pouvais pas l'arrêter. navigationInterceptor
semble être une solution, mais je ne sais pas si quelque chose d'autre attend de se casser. S'il est mélangé avec le, async requests
il introduira à nouveau le problème, je pense.
display
soit none
ou inline
.
'md-spinner' is not a known element:
. Je suis assez nouveau sur Angular. Pourriez-vous s'il vous plaît me dire quelle pourrait être l'erreur?
MISE À JOUR: 3 Maintenant que j'ai mis à niveau vers le nouveau routeur, l' approche de @borislemke ne fonctionnera pas si vous utilisez CanDeactivate
guard. Je me dégrade à mon ancienne méthode, ie:
cette réponse
UPDATE2 : Les événements de routeur dans le nouveau routeur semblent prometteurs et la réponse de @borislemke semble couvrir l'aspect principal de l'implémentation de spinner, je ne l'ai pas testé mais je le recommande.
UPDATE1: J'ai écrit cette réponse à l'époque 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 cela s'est retourné contre lui 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
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 { }
Spinner-Component (s'abonnera au service Spinner pour changer la valeur d'actif 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 (amorcez ce service)
Définissez une observable à souscrire par composant spinner pour changer le statut en cas de changement, et 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 tous les autres itinéraires
(é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();
}
}
spinner-service
instant, vous avez juste besoin d'autres parties pour le faire fonctionner. Et rappelez-vous que c'est pourangular2-rc-1
Pourquoi ne pas simplement 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 juste
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 la sortie du routeur , donc avec un simple sélecteur css, nous pouvons afficher et masquer le chargement.
Si une logique spéciale est requise pour la première route uniquement, vous pouvez effectuer les opérations suivantes:
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 masquer le pied de page de ma page, qui apparaissait autrement en haut de la page. De plus, si vous souhaitez uniquement un chargeur pour la première page, vous pouvez l'utiliser.
Vous pouvez également utiliser cette solution existante . La démo est là . Cela ressemble à la barre de chargement YouTube. Je viens de le trouver et de l'ajouter à mon propre projet.