Wie zeige ich einen Ladebildschirm an, wenn ich eine Route in Angular 2 ändere?
Wie zeige ich einen Ladebildschirm an, wenn ich eine Route in Angular 2 ändere?
Antworten:
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. NavigationCancel
und NavigationError
Ihren 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 *ngIf
den Spinner bedingt anzuzeigen, könnten wir Angulars nutzen NgZone
und Renderer
den 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 *ngIf
oder einer async
Pipe 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>
router navigation
und it triggering the spinner
dann nicht aufhören zu können. navigationInterceptor
scheint 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 requests
wird, wird es das Problem wieder einführen, denke ich.
display
entweder none
oder zu verwenden inline
.
'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?
UPDATE: 3 Nachdem ich auf einen neuen Router aktualisiert habe , funktioniert der Ansatz von @borislemke nicht, wenn Sie CanDeactivate
Guard 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();
}
}
spinner-service
jetzt 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
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.
Wenn Sie eine spezielle Logik für die erste Route , können Sie Folgendes tun:
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.
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.