Làm cách nào để hiển thị màn hình tải khi tôi thay đổi tuyến đường trong Angular 2?
Làm cách nào để hiển thị màn hình tải khi tôi thay đổi tuyến đường trong Angular 2?
Câu trả lời:
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ư NavigationCancel
và NavigationError
để 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 NgZone
và Renderer
bậ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 *ngIf
hoặ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>
router navigation
và it triggering the spinner
và sau đó không thể ngăn chặn nó. navigationInterceptor
có 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 requests
nó sẽ giới thiệu vấn đề một lần nữa, tôi nghĩ.
display
một trong hai none
hoặc inline
.
'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?
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 CanDeactivate
bả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();
}
}
spinner-service
bâ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
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.
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:
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.
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.