webentwicklung-frage-antwort-db.com.de

Ladebildschirm anzeigen, wenn Sie in Angular 2 zwischen den Routen navigieren

Wie zeige ich einen Ladebildschirm an, wenn ich in Angular 2 eine Route ändere?

102
think123

Der aktuelle Angular Router bietet Navigationsereignisse. Sie können diese abonnieren und entsprechend Änderungen an der Benutzeroberfläche vornehmen. Denken Sie daran, bei anderen Ereignissen wie NavigationCancel und NavigationError zu zählen, um den Spinner anzuhalten, 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) {
    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.component.html - Ihre Root-Ansicht

<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 Verbesserte Antwort : Wenn Sie Wert auf Leistung legen, gibt es eine bessere Methode. Die Implementierung ist etwas umständlicher, aber die Leistungsverbesserung lohnt die zusätzliche Arbeit. Anstatt *ngIf zum bedingten Anzeigen des Spinners zu verwenden, können wir Angulars NgZone und Renderer verwenden, um den Spinner ein-/auszuschalten, wodurch die Änderungserkennung von Angular umgangen wird, wenn der Status des Spinners geändert wird. Ich fand das, um die Animation weicher zu machen, verglichen mit *ngIf oder einer async-Pipe.

Dies ist ähnlich zu meiner vorherigen Antwort mit einigen Änderungen:

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((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.component.html - Ihre Root-Ansicht

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

UPDATE: 3 Nachdem ich ein Upgrade auf einen neuen Router durchgeführt habe, funktioniert der Ansatz von @borislemke nicht, wenn Sie CanDeactivate guard verwenden. Ich degradiere zu meiner alten Methode, ie: dieser Antwort

UPDATE2: Router-Ereignisse in neuen Routern 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 Ära von Old-Router geschrieben, als es bisher nur ein Ereignis gab, route-changed über router.subscribe() benachrichtigt wurde. Ich fühlte auch den untenstehenden Ansatz überlastet und versuchte es mit nur router.subscribe() und backfired, weil canceled navigation nicht gefunden werden konnte. Ich musste also auf eine langwierige Herangehensweise zurückgreifen (Doppelarbeit).


Wenn Sie sich in Angular2 auskennen, brauchen Sie dies


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 (diesen Dienst bootstrap)

Definieren Sie ein Observable, das von der Spinner-Komponente abonniert werden soll, um den Status bei einer Änderung zu ändern, und geben Sie an, ob der Spinner aktiv und inaktiv ist.

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

Komponenten aller anderen Routen 

(Probe):

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();
  }
}
38
Ankit Singh

Warum nicht einfach nur 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 sogar 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 router-outlet erscheinen. Mit einem einfachen CSS-Selektor können wir das Laden ein- und ausblenden.

10
Milad

Wenn Sie nur für die first route eine spezielle Logik benötigen, 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 hatte das Bedürfnis, meine Seitenfußzeile auszublenden, die sonst oben auf der Seite angezeigt wurde. Wenn Sie nur einen Loader für die erste Seite benötigen, können Sie diesen verwenden.

1
Simon_Weaver

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

0
walkerbox