Jak wyświetlić ekran ładowania, gdy zmienię trasę w Angular 2?
Jak wyświetlić ekran ładowania, gdy zmienię trasę w Angular 2?
Odpowiedzi:
Obecny router Angular zapewnia zdarzenia nawigacji. Możesz je zasubskrybować i odpowiednio zmienić interfejs użytkownika. Pamiętaj, aby liczyć w innych wydarzeniach, takich jak NavigationCanceliNavigationError zatrzymać swój spinner w przypadku niepowodzenia przejść między routerami.
app.component.ts - Twój główny komponent
...
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 - Twój widok główny
<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>
Poprawiona wydajność Odpowiedź : Jeśli zależy Ci na wydajności, istnieje lepsza metoda, która jest nieco bardziej żmudna do wdrożenia, ale poprawa wydajności będzie warta dodatkowej pracy. Zamiast używać *ngIfdo warunkowego pokazywania tarczy, moglibyśmy wykorzystać Angular's NgZonei Rendererwłączyć / wyłączyć tarczę, która obejdzie wykrywanie zmiany Angulara, gdy zmienimy stan tarczy. Okazało się, że to sprawia, że animacja jest płynniejsza w porównaniu z użyciem *ngIflubasync rury.
Jest to podobne do mojej poprzedniej odpowiedzi z kilkoma poprawkami:
app.component.ts - Twój główny komponent
...
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 - Twój widok główny
<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 navigationi it triggering the spinner, a następnie nie jest w stanie go powstrzymać. navigationInterceptorwydaje się rozwiązaniem, ale nie jestem pewien, czy coś innego czeka, aby się zepsuć. async requestsMyślę, że zmieszanie z tym ponownie spowoduje problem.
displayalbo nonelub inline.
'md-spinner' is not a known element:. Jestem całkiem nowy w Angular. Czy możesz mi powiedzieć, jaki może być błąd?
AKTUALIZACJA: 3 Teraz, gdy zaktualizowałem router do nowego, podejście @borislemke nie będzie działać, jeśli używasz CanDeactivateguard. Poniżam moją starą metodęie: ta odpowiedź
Update2 wydarzenia router nowego routera i wyglądają obiecująco: odpowiedź przez @borislemke wydaje się obejmować główny aspekt realizacji wirowania, ja havent't przetestowane ale polecam go.
UPDATE1: Napisałem tę odpowiedź w erze Old-Router, kiedy było tylko jedno zdarzenie route-changedpowiadamiane przez router.subscribe(). Czułem również przeciążenie poniższego podejścia i próbowałem to zrobić używając tylko router.subscribe(), ale przyniosło odwrotny skutek, ponieważ nie było sposobu na wykrycie canceled navigation. Musiałem więc wrócić do długiego podejścia (podwójna praca).
Jeśli znasz się na Angular2, to jest to, czego potrzebujesz
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]);
Główny składnik - (MojaAplikacja)
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 (subskrybuje usługę Spinner, aby odpowiednio zmienić wartość aktywnego)
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;
});
}
}
Usługa Spinner (uruchom tę usługę)
Zdefiniuj obserwowalny, który ma być subskrybowany przez składnik pokrętła, aby zmienić stan w przypadku zmiany, i użyj funkcji, aby poznać i ustawić pokrętło jako aktywne / nieaktywne.
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;
}
}
Składniki wszystkich innych tras
(próba):
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-servicerazie potrzebujesz tylko innych części, aby działał. I pamiętaj, że to dlaangular2-rc-1
Dlaczego nie po prostu użyć prostego CSS:
<router-outlet></router-outlet>
<div class="loading"></div>
I w twoich stylach:
div.loading{
height: 100px;
background-color: red;
display: none;
}
router-outlet + div.loading{
display: block;
}
Lub nawet możemy to zrobić dla pierwszej odpowiedzi:
<router-outlet></router-outlet>
<spinner-component></spinner-component>
A potem po prostu
spinner-component{
display:none;
}
router-outlet + spinner-component{
display: block;
}
Sztuczka polega na tym, że nowe trasy i komponenty będą zawsze pojawiać się po wyjściu routera , więc za pomocą prostego selektora css możemy pokazać i ukryć ładowanie.
Jeśli masz specjalną logikę wymaganą tylko dla pierwszej trasy, możesz wykonać następujące czynności:
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');
});
Musiałem to ukryć stopkę mojej strony, która w przeciwnym razie pojawiałaby się u góry strony. Również jeśli chcesz ładować tylko pierwszą stronę, możesz tego użyć.
Możesz również skorzystać z tego istniejącego rozwiązania . Demo jest tutaj . Wygląda jak pasek ładowania youtube. Po prostu go znalazłem i dodałem do swojego projektu.