Ladebildschirm anzeigen, wenn Sie in Angular 2 zwischen Routen navigieren

Antworten:

196

Der aktuelle Angular Router bietet Navigationsereignisse. Sie können diese abonnieren und entsprechende Änderungen an der Benutzeroberfläche vornehmen. Denken Sie daran, bei anderen Ereignissen zu zählen, z. B. NavigationCancelund NavigationErrorIhren Spinner zu stoppen, falls Routerübergänge fehlschlagen.

app.component.ts - Ihre Stammkomponente

...
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 - Ihre Stammansicht

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

Antwort zur Leistungsverbesserung : Wenn Sie sich für die Leistung interessieren, gibt es eine bessere Methode. Die Implementierung ist etwas mühsamer, aber die Leistungsverbesserung ist die zusätzliche Arbeit wert. Anstatt *ngIfden Spinner bedingt anzuzeigen, könnten wir Angulars nutzen NgZoneund Rendererden Spinner ein- und ausschalten, wodurch die Änderungserkennung von Angular umgangen wird, wenn wir den Status des Spinners ändern. Ich fand dies, um die Animation im Vergleich zur Verwendung *ngIfoder einer asyncPipe flüssiger zu machen .

Dies ähnelt meiner vorherigen Antwort mit einigen Verbesserungen:

app.component.ts - Ihre Stammkomponente

...
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 - Ihre Stammansicht

<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>
Borislemke
quelle
1
Großartig, ich schätze Ihren Ansatz. Es hat mich getroffen , bevor ich meine lange Methode mit dieser ansprechenden Idee ersetzt, als ich zurückkommen musste weil ich die Kontrolle verloren über router navigationund it triggering the spinnerdann nicht aufhören zu können. navigationInterceptorscheint eine Lösung zu sein, aber ich bin mir nicht sicher, ob etwas anderes darauf wartet, kaputt zu gehen. Wenn es mit dem gemischt async requestswird, wird es das Problem wieder einführen, denke ich.
Ankit Singh
1
Vielleicht hat das irgendwann geklappt? Es funktioniert jetzt nicht mit Angular 2.4.8. Die Seite ist synchron. Der Spinner wird erst gerendert, wenn die gesamte Seite / übergeordnete Komponente gerendert wurde, und das bei NavigationEnd. Das besiegt den Punkt des Spinners
techguy2000
1
Die Verwendung von Deckkraft ist keine gute Wahl. Optisch ist es in Ordnung, aber in Chrome können Sie nicht auf die Schaltfläche zugreifen, wenn sich das Ladebild / -symbol über einer Schaltfläche oder einem Text befindet. Ich habe es geändert, um displayentweder noneoder zu verwenden inline.
Im1dermike
2
Ich mag diesen Ansatz, guter Job Mann! Aber ich habe ein Problem und verstehe nicht, warum, wenn ich die Animation auf NavigationEnd nicht wechsle, ich sehe, wie der Spinner geladen wird, aber wenn ich auf false wechsle, ändern sich die Routen so schnell, dass ich nicht einmal Animationen sehen kann :( Ich habe es versucht mit sogar Netzwerk-Throteling, um die Verbindung zu verlangsamen, aber es bleibt immer noch das gleiche :( überhaupt kein Laden. Könnten Sie mir bitte Vorschläge dazu geben. Ich
steuere
1
Ich habe diesen Fehler erhalten, als ich Ihren Code kopiert habe 'md-spinner' is not a known element:. Ich bin ziemlich neu in Angular. Könnten Sie mir bitte sagen, was der Fehler sein könnte?
Manu Chadha
39

UPDATE: 3 Nachdem ich auf einen neuen Router aktualisiert habe , funktioniert der Ansatz von @borislemke nicht, wenn Sie CanDeactivateGuard verwenden. Ich erniedrige mich zu meiner alten Methode, ie:dieser Antwort

UPDATE2 : Router-Ereignisse in New-Router sehen vielversprechend aus und die Antwort von @borislemke scheint den Hauptaspekt der Spinner-Implementierung abzudecken. Ich habe es nicht getestet, aber ich empfehle es.

UPDATE1: Ich habe diese Antwort in der Zeit geschrieben Old-Router, als es nur ein Ereignis gab route-changed, über das benachrichtigt wurde router.subscribe(). Ich fühlte auch eine Überlastung des folgenden Ansatzes und versuchte, ihn nur mit zu verwenden router.subscribe(), und er schlug fehl, weil es keine Möglichkeit gab, ihn zu erkennen canceled navigation. Also musste ich zu einem langwierigen Ansatz zurückkehren (Doppelarbeit).


Wenn Sie sich in Angular2 auskennen, ist dies das, was Sie brauchen


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]);

Root Component- (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-Komponente (abonniert den Spinner-Service, um den Wert von active entsprechend zu ändern)

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 diesen Service)

Definieren Sie eine Observable, die von der Spinner-Komponente abonniert werden soll, um den Status bei Änderung zu ändern, und die Funktion, um den Spinner zu kennen und aktiv / inaktiv zu setzen.

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

Alle anderen Routenkomponenten

(Stichprobe):

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();
  }
}
Ankit Singh
quelle
siehe auch das. Ich weiß nicht, wie viel Animation standardmäßig Winkelunterstützung bietet.
Ankit Singh
Kannst du mir helfen, den Service für den Spinner aufzuschreiben? Ich kann die Animationen und so weiter in CSS selbst machen, aber es wäre schön, wenn Sie mir beim Service helfen könnten.
Lucas
siehe diese Implemetation auch, gleich, aber nicht genau
Ankit Singh
1
Ich habe spinner-servicejetzt Code hinzugefügt, den Sie nur für andere Teile benötigen, damit es funktioniert. Und denken Sie daran, es ist fürangular2-rc-1
Ankit Singh
1
Eigentlich ist das großartig und funktioniert. Mein Tipp zu Testzwecken: Sie können den Stopp des Spinners mit setTimeout (() => this.spinner.stop (), 5000) in ngOnInit
Jhonatas Kleinkauff am
10

Warum nicht einfach einfaches CSS verwenden:

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

Und in deinen Stilen:

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

Oder wir können dies für die erste Antwort tun:

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

Und dann einfach nur

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

Der Trick dabei ist, dass die neuen Routen und Komponenten immer nach dem Router-Outlet angezeigt werden. Mit einem einfachen CSS-Selektor können wir das Laden ein- und ausblenden.

Milad
quelle
<router-outlet> erlaubt es uns nicht, Werte von der Komponente an die übergeordnete Komponente zu übergeben, daher ist es etwas komplex, das Ladediv zu verbergen.
Praveen Rana
1
Es kann auch sehr ärgerlich sein, wenn Ihre Anwendung viele Routenänderungen aufweist, um den Spinner jedes Mal sofort zu sehen. Es ist besser, RxJs zu verwenden und einen entprellen Timer einzustellen, damit er erst nach einer kurzen Verzögerung angezeigt wird.
Simon_Weaver
2

Wenn Sie eine spezielle Logik für die erste Route , können Sie Folgendes tun:

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

Ich musste meine Seitenfußzeile ausblenden, die ansonsten oben auf der Seite angezeigt wurde. Auch wenn Sie nur einen Lader für die erste Seite möchten, können Sie diesen verwenden.

Simon_Weaver
quelle
0

Sie können auch diese vorhandene Lösung verwenden . Die Demo ist da . Es sieht aus wie eine YouTube-Ladeleiste. Ich habe es gerade gefunden und meinem eigenen Projekt hinzugefügt.

Jonatan Drache
quelle
hilft es bei Resolvern? Mein Problem ist, dass Resolver zwar die Daten zurückbringen, ich jedoch keinen Spinner irgendwo anzeigen kann, da die eigentliche Zielkomponente ngOninit noch nicht aufgerufen wurde !! Meine Idee war es, den Spinner in ngOnInit anzuzeigen und auszublenden, sobald aufgelöste Daten aus dem
Routenabonnement zurückgegeben wurden