Bagaimana cara menampilkan layar pemuatan ketika saya mengubah rute di Angular 2?
Bagaimana cara menampilkan layar pemuatan ketika saya mengubah rute di Angular 2?
Jawaban:
Router Sudut saat ini menyediakan Acara Navigasi. Anda dapat berlangganan ini dan membuat perubahan UI yang sesuai. Ingatlah untuk menghitung dalam Peristiwa lain seperti NavigationCancel
dan NavigationError
untuk menghentikan pemintal jika transisi router gagal.
app.component.ts - komponen root Anda
...
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 - tampilan root Anda
<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>
Jawaban yang Ditingkatkan Kinerja : Jika Anda peduli dengan kinerja, ada metode yang lebih baik, ini sedikit lebih membosankan untuk diterapkan tetapi peningkatan kinerja akan sepadan dengan kerja ekstra. Alih-alih menggunakan *ngIf
untuk menampilkan spinner secara bersyarat, kita dapat memanfaatkan Angular's NgZone
dan Renderer
untuk mengaktifkan / menonaktifkan spinner yang akan melewati deteksi perubahan Angular saat kita mengubah status spinner. Saya menemukan ini untuk membuat animasi lebih halus dibandingkan dengan menggunakan *ngIf
atau async
pipa.
Ini mirip dengan jawaban saya sebelumnya dengan beberapa penyesuaian:
app.component.ts - komponen root Anda
...
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 - tampilan root Anda
<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
dan it triggering the spinner
kemudian tidak bisa menghentikannya. navigationInterceptor
sepertinya solusi tetapi saya tidak yakin apakah ada hal lain yang menunggu untuk dipecahkan. Jika dicampur dengan async requests
itu akan menimbulkan masalah lagi, saya kira.
display
salah satu none
atau inline
.
'md-spinner' is not a known element:
. Saya cukup baru di Angular. Bisakah Anda memberi tahu saya apa yang mungkin menjadi kesalahannya?
UPDATE: 3 Sekarang saya telah mengupgrade ke Router baru, pendekatan @borislemke tidak akan berfungsi jika Anda menggunakan CanDeactivate
guard. Saya merendahkan metode lama saya, ie:
jawaban ini
UPDATE2 : Peristiwa router di new-router terlihat menjanjikan dan jawaban dari @borislemke tampaknya mencakup aspek utama implementasi spinner, saya belum mengujinya tetapi saya merekomendasikannya.
UPDATE1: Saya menulis jawaban ini di era Old-Router
, ketika dulu hanya ada satu peristiwa yang route-changed
diberitahukan melalui router.subscribe()
. Saya juga merasa kelebihan pendekatan di bawah ini dan mencoba melakukannya hanya dengan menggunakan router.subscribe()
, dan itu menjadi bumerang karena tidak ada cara untuk mendeteksinya canceled navigation
. Jadi saya harus kembali ke pendekatan panjang (kerja ganda).
Jika Anda tahu jalan di Angular2, inilah yang Anda perlukan
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]);
Komponen 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 (akan berlangganan Spinner-service untuk mengubah nilai aktif yang sesuai)
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 layanan ini)
Tentukan observable untuk dilanggan oleh spinner-component untuk mengubah status pada perubahan, dan berfungsi untuk mengetahui dan mengatur spinner aktif / tidak aktif.
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;
}
}
Semua Komponen Rute Lainnya
(Sampel):
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
saat ini Anda hanya perlu ke bagian lain untuk membuatnya berfungsi. Dan ingat ini untukangular2-rc-1
Mengapa tidak hanya menggunakan css sederhana:
<router-outlet></router-outlet>
<div class="loading"></div>
Dan dalam gaya Anda:
div.loading{
height: 100px;
background-color: red;
display: none;
}
router-outlet + div.loading{
display: block;
}
Atau bahkan kita bisa melakukan ini untuk jawaban pertama:
<router-outlet></router-outlet>
<spinner-component></spinner-component>
Dan kemudian baru saja
spinner-component{
display:none;
}
router-outlet + spinner-component{
display: block;
}
Triknya di sini adalah, rute dan komponen baru akan selalu muncul setelah outlet-router , jadi dengan pemilih css sederhana kita dapat menampilkan dan menyembunyikan pemuatan.
Jika Anda memiliki logika khusus yang diperlukan untuk rute pertama, hanya Anda yang dapat melakukan hal berikut:
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');
});
Saya membutuhkan ini untuk menyembunyikan footer halaman saya, yang sebaliknya muncul di bagian atas halaman. Juga jika Anda hanya menginginkan sebuah loader untuk halaman pertama, Anda dapat menggunakan ini.
Anda juga bisa menggunakan solusi yang ada ini . Demo ada di sini . Sepertinya bilah memuat youtube. Saya baru saja menemukannya dan menambahkannya ke proyek saya sendiri.