Come faccio a mostrare una schermata di caricamento quando cambio un percorso in Angular 2?
Come faccio a mostrare una schermata di caricamento quando cambio un percorso in Angular 2?
Risposte:
L'attuale router angolare fornisce eventi di navigazione. Puoi iscriverti a questi e apportare modifiche all'interfaccia utente di conseguenza. Ricorda di contare in altri eventi come NavigationCancel
e NavigationError
di fermare il tuo spinner nel caso in cui le transizioni del router falliscano.
app.component.ts : il componente principale
...
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 - la tua vista radice
<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>
Risposta alle prestazioni migliorata : se ti preoccupi delle prestazioni, esiste un metodo migliore, è leggermente più noioso da implementare, ma il miglioramento delle prestazioni varrà il lavoro extra. Invece di usare *ngIf
per mostrare condizionatamente lo spinner, potremmo sfruttare Angular NgZone
e Renderer
accendere / spegnere lo spinner che aggirerà il rilevamento del cambiamento di Angular quando cambiamo lo stato dello spinner. Ho trovato questo per rendere l'animazione più fluida rispetto all'utilizzo *ngIf
o alla async
pipe.
Questo è simile alla mia precedente risposta con alcune modifiche:
app.component.ts : il componente principale
...
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 - la tua vista radice
<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
e it triggering the spinner
poi non essere in grado di fermarlo. navigationInterceptor
sembra una soluzione ma non sono sicuro se qualcos'altro sta aspettando di rompersi. Se mescolato con async requests
questo introdurrà di nuovo il problema, penso.
display
uno none
o inline
.
'md-spinner' is not a known element:
. Sono abbastanza nuovo per Angular. Potresti dirmi quale potrebbe essere l'errore?
AGGIORNAMENTO: 3 Ora che ho eseguito l' aggiornamento al nuovo router, l'approccio di @borislemke non funzionerà se si utilizza CanDeactivate
guard. Sto degradando al mio vecchio metodo, ie:
questa risposta
UPDATE2 : Gli eventi del router nel nuovo router sembrano promettenti e la risposta di @borislemke sembra coprire l'aspetto principale dell'implementazione dello spinner, non l'ho ancora testato ma lo consiglio.
UPDATE1: ho scritto questa risposta nell'era di Old-Router
, quando c'era solo un evento route-changed
notificato tramite router.subscribe()
. Ho anche sentito un sovraccarico dell'approccio di seguito e ho provato a farlo usando solo router.subscribe()
, e ha fallito perché non c'era modo di rilevare canceled navigation
. Quindi ho dovuto tornare all'approccio lungo (doppio lavoro).
Se conosci Angular2, questo è ciò di cui hai bisogno
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]);
Componente root- (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 (si abbonerà al servizio Spinner per modificare di conseguenza il valore di active)
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 (avvia questo servizio)
Definire un osservabile che deve essere sottoscritto dal componente Spinner per cambiare lo stato al momento del cambio e funzione per conoscere e impostare lo spinner attivo / inattivo.
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;
}
}
Tutti gli altri componenti delle rotte
(campione):
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
ora, per farlo funzionare devi solo aggiungere altre parti. E ricorda che è perangular2-rc-1
Perché non usare semplicemente CSS:
<router-outlet></router-outlet>
<div class="loading"></div>
E nei tuoi stili:
div.loading{
height: 100px;
background-color: red;
display: none;
}
router-outlet + div.loading{
display: block;
}
O persino possiamo farlo per la prima risposta:
<router-outlet></router-outlet>
<spinner-component></spinner-component>
E poi semplicemente
spinner-component{
display:none;
}
router-outlet + spinner-component{
display: block;
}
Il trucco qui è, i nuovi percorsi e componenti appariranno sempre dopo l' uscita del router , quindi con un semplice selettore CSS possiamo mostrare e nascondere il caricamento.
Se è richiesta una logica speciale solo per il primo percorso, è possibile effettuare le seguenti operazioni:
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');
});
Avevo bisogno di nascondere il piè di pagina, che altrimenti sarebbe apparso nella parte superiore della pagina. Inoltre, se vuoi solo un caricatore per la prima pagina, puoi usarlo.
È inoltre possibile utilizzare questa soluzione esistente . La demo è qui . Sembra che la barra di caricamento di YouTube. L'ho appena trovato e l'ho aggiunto al mio progetto.