Tampilkan layar pemuatan saat menavigasi di antara rute di Angular 2

Jawaban:

196

Router Sudut saat ini menyediakan Acara Navigasi. Anda dapat berlangganan ini dan membuat perubahan UI yang sesuai. Ingatlah untuk menghitung dalam Peristiwa lain seperti NavigationCanceldan NavigationErroruntuk 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 *ngIfuntuk menampilkan spinner secara bersyarat, kita dapat memanfaatkan Angular's NgZonedan Rendereruntuk 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 *ngIfatau asyncpipa.

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>
borislemke.dll
sumber
1
Bagus, saya menghargai pendekatan Anda. Ini telah memukul saya sebelumnya dan saya menggantikan metode lama saya dengan ide menarik ini, daripada saya harus kembali karena saya kehilangan kontrol atas router navigationdan it triggering the spinnerkemudian tidak bisa menghentikannya. navigationInterceptorsepertinya solusi tetapi saya tidak yakin apakah ada hal lain yang menunggu untuk dipecahkan. Jika dicampur dengan async requestsitu akan menimbulkan masalah lagi, saya kira.
Ankit Singh
1
Mungkin ini berhasil di beberapa titik? Ini tidak berfungsi sekarang dengan Angular 2.4.8. Halaman ini sinkron. Spinner tidak akan dirender sampai seluruh halaman / komponen induk dirender, dan di NavigationEnd. Itu mengalahkan poin pemintal
techguy2000
1
Menggunakan opacity bukanlah pilihan yang bagus. Secara visual tidak apa-apa, tetapi di Chrome, jika gambar / ikon pemuatan berada di atas tombol atau teks, Anda tidak dapat mengakses tombol tersebut. Saya mengubahnya untuk menggunakan displaysalah satu noneatau inline.
im1dermike
2
Saya suka pendekatan ini, kerja bagus! Tetapi saya memiliki masalah dan saya tidak mengerti mengapa, jika saya tidak akan mengaktifkan animasi di NavigationEnd, saya dapat melihat pemintal memuat, tetapi jika saya beralih ke false, rute berubah sangat cepat sehingga saya bahkan tidak dapat melihat animasi apa pun :( Saya sudah mencoba bahkan dengan throteling jaringan untuk memperlambat koneksi tetapi masih tetap sama :( tidak memuat sama sekali. Bisakah Anda memberi saya saran tentang hal ini. Saya mengontrol animasi dengan menambahkan dan menghapus kelas pada elemen pemuatan. Terima kasih
d123546
1
Saya mendapat kesalahan ini ketika saya menyalin kode Anda 'md-spinner' is not a known element:. Saya cukup baru di Angular. Bisakah Anda memberi tahu saya apa yang mungkin menjadi kesalahannya?
Manu Chadha
40

UPDATE: 3 Sekarang saya telah mengupgrade ke Router baru, pendekatan @borislemke tidak akan berfungsi jika Anda menggunakan CanDeactivateguard. 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-changeddiberitahukan 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();
  }
}
Ankit Singh
sumber
lihat ini juga. Saya tidak seberapa banyak Animasi yang mendukung sudut secara default.
Ankit Singh
dapatkah Anda membantu saya menulis layanan untuk pemintal? Saya dapat melakukan animasi dan yang lainnya di CSS sendiri, tetapi alangkah baiknya jika Anda dapat membantu saya dengan layanan ini.
Lucas
lihat penerapan ini juga, tetapi tidak persis sama
Ankit Singh
1
Saya telah menambahkan beberapa kode untuk spinner-servicesaat ini Anda hanya perlu ke bagian lain untuk membuatnya berfungsi. Dan ingat ini untukangular2-rc-1
Ankit Singh
1
sebenarnya, ini luar biasa dan berfungsi, tip saya untuk tujuan pengujian Anda dapat menunda penghentian pemintal dengan setTimeout (() => this.spinner.stop (), 5000) di ngOnInit
Jhonatas Kleinkauff
10

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.

Milad
sumber
<router-outlet> tidak mengizinkan kita untuk meneruskan nilai dari komponen ke komponen induk, jadi akan agak rumit untuk menyembunyikan div pemuatan.
Praveen Rana
1
Juga bisa sangat menjengkelkan jika aplikasi Anda memiliki banyak perubahan rute untuk melihat spinner setiap saat secara instan. Lebih baik menggunakan RxJs dan mengatur pengatur waktu debounce sehingga hanya muncul setelah penundaan singkat.
Simon_Weaver
2

Jika Anda memiliki logika khusus yang diperlukan untuk rute pertama, hanya Anda yang dapat melakukan hal berikut:

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

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.

Simon_Weaver
sumber
0

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.

Jonatan Dragon
sumber
apakah itu membantu dengan resolver? Masalah saya adalah saat resolver mengembalikan data, saya tidak dapat menampilkan spinner di mana pun karena komponen target sebenarnya ngOninit belum dipanggil !! Ide saya adalah menampilkan spinner di ngOnInit dan menyembunyikannya setelah data terselesaikan dikembalikan dari langganan rute
kuldeep