Mostra la schermata di caricamento durante la navigazione tra le rotte in Angular 2


Risposte:


196

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 NavigationCancele NavigationErrordi 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 *ngIfper mostrare condizionatamente lo spinner, potremmo sfruttare Angular NgZonee Rendereraccendere / 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 *ngIfo alla asyncpipe.

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>

1
Ottimo, apprezzo il tuo approccio. Mi ha colpito prima e ho sostituito il mio metodo lungo con questa idea accattivante, che ho dovuto tornare becuase ho perso il controllo su router navigatione it triggering the spinnerpoi non essere in grado di fermarlo. navigationInterceptorsembra una soluzione ma non sono sicuro se qualcos'altro sta aspettando di rompersi. Se mescolato con async requestsquesto introdurrà di nuovo il problema, penso.
Ankit Singh,

1
Forse questo ha funzionato ad un certo punto? Non funziona ora con Angular 2.4.8. La pagina è sincrona. Lo Spinner non viene renderizzato fino a quando non viene eseguito il rendering dell'intero componente pagina / padre e che su NavigationEnd. Ciò sconfigge il punto dello spinner
techguy2000,

1
L'uso dell'opacità non è un'ottima scelta. Visivamente, va bene, ma in Chrome, se l'immagine / icona di caricamento si trova sopra un pulsante o testo, non è possibile accedere al pulsante. Ho cambiato in modo da utilizzare displayuno noneo inline.
im1dermike,

2
Mi piace questo approccio, buon lavoro amico! Ma ho un problema e non capisco perché, se non cambio l'animazione su NavigationEnd, riesco a vedere il caricamento dello spinner, ma se passo a falso, le rotte cambiano così velocemente da non riuscire nemmeno a vedere alcuna animazione :( Ho provato con una rete uniforme che rallenta la connessione ma rimane sempre lo stesso :( nessun caricamento. Potresti darmi qualche suggerimento su questo per favore. Controllo le animazioni aggiungendo e rimuovendo la classe nell'elemento di caricamento. Grazie
d123546

1
Ho ricevuto questo errore quando ho copiato il tuo codice 'md-spinner' is not a known element:. Sono abbastanza nuovo per Angular. Potresti dirmi quale potrebbe essere l'errore?
Manu Chadha,

39

AGGIORNAMENTO: 3 Ora che ho eseguito l' aggiornamento al nuovo router, l'approccio di @borislemke non funzionerà se si utilizza CanDeactivateguard. 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-changednotificato 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();
  }
}

vedi anche questo . Non so quanto Animation supporta il supporto angolare per impostazione predefinita.
Ankit Singh,

mi potete aiutare a scrivere il servizio per lo spinner? Posso fare da solo le animazioni e quant'altro nel CSS, ma sarebbe bello se tu potessi aiutarmi con il servizio.
Lucas,

vedi anche questa implementazione , uguale ma non esattamente
Ankit Singh,

1
Ho aggiunto un po 'di codice per spinner-serviceora, per farlo funzionare devi solo aggiungere altre parti. E ricorda che è perangular2-rc-1
Ankit Singh,

1
in realtà, questo è fantastico e funziona, il mio consiglio a scopo di test è possibile ritardare l'arresto dello spinner con setTimeout (() => this.spinner.stop (), 5000) in ngOnInit
Jhonatas Kleinkauff

10

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.


<router-outlet> non ci consente di passare il valore dal componente al componente padre, quindi sarà un po 'complesso nascondere il div di caricamento.
Praveen Rana,

1
Inoltre, può essere estremamente fastidioso se l'applicazione presenta molte modifiche al percorso per vedere immediatamente lo spinner ogni volta. È meglio usare RxJs e impostare un timer rimbalzato in modo che appaia solo dopo un breve ritardo.
Simon_Weaver,

2

Se è richiesta una logica speciale solo per il primo percorso, è possibile effettuare le seguenti operazioni:

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');
                   });

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.


0

È 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.


aiuta con i risolutori? Il mio problema è che mentre i resolver riportano i dati, non riesco a mostrare lo spinner da nessuna parte perché il componente target attuale ngOninit non è stato ancora chiamato !! La mia idea era quella di mostrare lo spinner in ngOnInit e nasconderlo una volta che i dati risolti sono stati restituiti dall'abbonamento alla rotta
kuldeep,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.