Hiển thị màn hình tải khi điều hướng giữa các tuyến đường trong Angular 2


Câu trả lời:


196

Bộ định tuyến Angular hiện tại cung cấp Sự kiện Điều hướng. Bạn có thể đăng ký những thứ này và thực hiện thay đổi UI cho phù hợp. Hãy nhớ tính vào các Sự kiện khác như NavigationCancelNavigationErrorđể dừng spinner của bạn trong trường hợp chuyển đổi bộ định tuyến không thành công.

app.component.ts - thành phần gốc của bạn

...
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 - chế độ xem gốc của bạn

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

Hiệu suất được cải thiện Trả lời : Nếu bạn quan tâm đến hiệu suất, có một phương pháp tốt hơn, sẽ hơi tẻ nhạt hơn khi thực hiện nhưng việc cải thiện hiệu suất sẽ đáng để làm thêm. Thay vì sử dụng *ngIfđể hiển thị spinner một cách có điều kiện, chúng ta có thể tận dụng Angular NgZoneRendererbật / tắt spinner để vượt qua phát hiện thay đổi của Angular khi chúng ta thay đổi trạng thái của spinner. Tôi tìm thấy điều này để làm cho hình ảnh động mượt mà hơn so với việc sử dụng *ngIfhoặc một asyncđường ống.

Điều này tương tự như câu trả lời trước đây của tôi với một số điều chỉnh:

app.component.ts - thành phần gốc của bạn

...
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 - chế độ xem gốc của bạn

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

1
Tuyệt vời, tôi đánh giá cao cách tiếp cận của bạn. Nó đã đánh tôi trước và tôi đã thay thế phương pháp kéo dài của tôi với ý tưởng hấp dẫn này, hơn tôi đã phải trở lại bởi vì tôi đã mất kiểm soát router navigationit triggering the spinnervà sau đó không thể ngăn chặn nó. navigationInterceptorcó vẻ như là một giải pháp nhưng tôi không chắc chắn nếu có thứ gì khác đang chờ để phá vỡ. Nếu trộn lẫn với async requestsnó sẽ giới thiệu vấn đề một lần nữa, tôi nghĩ.
Ankit Singh

1
Có lẽ điều này làm việc tại một số điểm? Hiện tại nó không hoạt động với Angular 2.4.8. Trang này là đồng bộ. Spinner không được hiển thị cho đến khi toàn bộ thành phần trang / cha mẹ hiển thị và tại NavigationEnd. Điều đó đánh bại điểm của spinner
techguy2000

1
Sử dụng độ mờ không phải là một lựa chọn tuyệt vời. Trực quan, nó ổn, nhưng trong Chrome, nếu hình ảnh / biểu tượng đang tải ở trên cùng của một nút hoặc văn bản, bạn không thể truy cập vào nút. Tôi đã thay đổi nó để sử dụng displaymột trong hai nonehoặc inline.
im1dermike

2
Tôi thích cách tiếp cận này, người đàn ông làm việc tốt! Nhưng tôi có một vấn đề và tôi không hiểu tại sao, nếu tôi không chuyển đổi hoạt hình trên NavigationEnd, tôi có thể thấy trình tải spinner, nhưng nếu tôi chuyển sang false, các tuyến thay đổi nhanh đến mức tôi thậm chí không thể thấy bất kỳ hoạt hình nào :( Tôi đã thử thậm chí với throteling mạng để làm chậm kết nối nhưng nó vẫn giống nhau :( không tải ở tất cả các bạn có thể cho tôi bất cứ đề nghị về vấn đề này xin vui lòng để kiểm soát các hình ảnh động bằng cách thêm và loại bỏ các lớp tại các yếu tố tải Cảm ơn...
d123546

1
Tôi đã gặp lỗi này khi tôi sao chép mã của bạn 'md-spinner' is not a known element:. Tôi còn khá mới với Angular. Bạn có thể vui lòng cho tôi biết những gì có thể là sai lầm?
Manu Chadha

39

CẬP NHẬT: 3 Bây giờ tôi đã nâng cấp lên Bộ định tuyến mới, phương pháp của @borislemke sẽ không hoạt động nếu bạn sử dụng CanDeactivatebảo vệ. Tôi đang làm mất phương pháp cũ của tôi, ie:câu trả lời này

CẬP NHẬT2 : Các sự kiện bộ định tuyến trong bộ định tuyến mới có vẻ đầy hứa hẹn và câu trả lời của @borislemke dường như đề cập đến khía cạnh chính của việc triển khai spinner, tôi chưa thử nghiệm nhưng tôi khuyên bạn nên thử nó.

CẬP NHẬT1: Tôi đã viết câu trả lời này trong thời đại Old-Router, khi trước đây chỉ có một sự kiện route-changedđược thông báo qua router.subscribe(). Tôi cũng cảm thấy quá tải về cách tiếp cận dưới đây và chỉ cố gắng thực hiện bằng cách sử dụng router.subscribe()và nó phản tác dụng vì không có cách nào để phát hiện canceled navigation. Vì vậy, tôi đã phải quay trở lại cách tiếp cận dài dòng (công việc gấp đôi).


Nếu bạn biết đường đi trong Angular2, đây là thứ bạn cần


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]);

Thành phần gốc- (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-Thành phần (sẽ đăng ký dịch vụ Spinner để thay đổi giá trị hoạt động tương ứng)

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

Dịch vụ Spinner (bootstrap dịch vụ này)

Xác định một quan sát được đăng ký bởi thành phần spinner để thay đổi trạng thái khi thay đổi, và chức năng để biết và đặt spinner hoạt động / không hoạt động.

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

Tất cả các thành phần của các tuyến khác

(mẫu vật):

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

thấy điều này cũng có. Tôi không có bao nhiêu Animation hỗ trợ góc theo mặc định.
Ankit Singh

bạn có thể giúp tôi viết dịch vụ cho spinner không? Tôi có thể tự làm hoạt hình và không có gì trong CSS, nhưng thật tuyệt nếu bạn có thể giúp tôi với dịch vụ.
Lucas

cũng thấy điều này , tương tự nhưng không chính xác
Ankit Singh

1
Tôi đã thêm một số mã cho spinner-servicebây giờ bạn chỉ cần các phần khác để làm cho nó hoạt động. Và hãy nhớ nó dành choangular2-rc-1
Ankit Singh

1
Trên thực tế, điều này thật tuyệt vời và hiệu quả, mẹo của tôi cho mục đích thử nghiệm, bạn có thể trì hoãn việc dừng spinner với setTimeout (() => this.spinner.stop (), 5000) trong ngOnInit
Jhonatas Kleinkauff

10

Tại sao không chỉ sử dụng css đơn giản:

<router-outlet></router-outlet>
<div class="loading"></div>

Và trong phong cách của bạn:

div.loading{
    height: 100px;
    background-color: red;
    display: none;
}
router-outlet + div.loading{
    display: block;
}

Hoặc thậm chí chúng ta có thể làm điều này cho câu trả lời đầu tiên:

<router-outlet></router-outlet>
<spinner-component></spinner-component>

Và sau đó chỉ đơn giản là

spinner-component{
   display:none;
}
router-outlet + spinner-component{
    display: block;
}

Mẹo ở đây là, các tuyến và thành phần mới sẽ luôn xuất hiện sau ổ cắm bộ định tuyến , vì vậy với bộ chọn css đơn giản, chúng ta có thể hiển thị và ẩn tải.


<bộ định tuyến ổ cắm> không cho phép chúng tôi chuyển giá trị từ thành phần này sang thành phần chính, do đó sẽ hơi phức tạp để ẩn div tải.
Praveen Rana

1
Ngoài ra, nó có thể cực kỳ khó chịu nếu ứng dụng của bạn có nhiều thay đổi tuyến đường để xem spinner mỗi lần ngay lập tức. Tốt hơn là sử dụng RxJ và đặt bộ hẹn giờ bị lỗi để nó chỉ xuất hiện sau một thời gian trễ ngắn.
Simon_Weaver

2

Nếu bạn có logic đặc biệt cần thiết cho lần đầu tiên tuyến bạn chỉ có thể làm như sau:

Ứng dụng

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

Tôi có nhu cầu này để ẩn chân trang của mình, cái mà xuất hiện ở đầu trang. Ngoài ra nếu bạn chỉ muốn một trình tải cho trang đầu tiên, bạn có thể sử dụng trang này.


0

Bạn cũng có thể sử dụng giải pháp hiện có này . Bản demo ở đây . Có vẻ như thanh tải youtube. Tôi chỉ tìm thấy nó và thêm nó vào dự án của riêng tôi.


nó có giúp gì với người giải quyết không? Vấn đề của tôi là trong khi các trình phân giải mang lại dữ liệu, tôi không thể hiển thị spinner ở bất cứ đâu vì thành phần đích thực sự ngOninit chưa được gọi !! Ý tưởng của tôi là hiển thị công cụ quay vòng trong ngOnInit và ẩn nó sau khi dữ liệu đã giải quyết được trả về từ đăng ký tuyến
kuldeep
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.