在Angular 2中的路线之间导航时显示加载屏幕

当我在Angular 2中更改路线时,如何显示加载屏幕?

目前的Angular路由器提供导航事件。 您可以订阅这些内容并相应地进行UI更改。 请记住在其他事件(如NavigationCancelNavigationError计数以防止路由器转换失败时的微调。

app.component.ts – 你的根组件

 ... 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) { router.events.subscribe((event: RouterEvent) => { this.navigationInterceptor(event) }) } // 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 – 您的根视图

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

性能改进的答案 :如果你关心性能有一个更好的方法,实现稍微乏味,但性能改进将是值得的额外工作。 我们可以利用Angular的NgZoneRenderer来打开/closuresSpinner,当我们改变Spinner的状态时,它将绕过Angular的变化检测,而不是使用*ngIf来有条件地显示Spinner。 我发现这使得animation比使用*ngIfasyncpipe道更平滑。

这与我之前的回答有些类似:

app.component.ts – 你的根组件

 ... 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((event: RouterEvent) => { this._navigationInterceptor(event) }) } // 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 – 您的根视图

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

更新:3现在,我已经升级到新的路由器,如果您使用CanDeactivate警卫, @borislemke的方法将无法正常工作。 我贬低我的旧方法, ie:这个答案

UPDATE2 :新路由器中的路由器事件看起来很有前途,下面的@borislemke (现在上面)的答案似乎涵盖了微调器实现的主要方面,我没有testing过,但我推荐它。

UPDATE1:我在Old-Router的时代写了这个答案,当时只有一个事件route-changed通过router.subscribe() route-changed通知。 我也觉得下面的方法超载,并尝试只使用router.subscribe() ,并因为没有办法检测到canceled navigationrouter.subscribe() 。 所以我不得不恢复到冗长的做法(双重工作)。


如果你在Angular2中知道你的方法,这就是你所需要的


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

根组件 – (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 { } 

微调组件 (将订阅微调服务来相应地改变激活的值)

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

微调服务 (Bootstrap这项服务)

定义一个由spinner-component订阅的observable来改变改变的状态,以及函数来知道和设置spinner活跃/不活跃。

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

所有其他路线的组成部分

(样品):

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

为什么不使用简单的CSS:

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

而在你的风格:

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

甚至我们可以做到这一点的第一个答案:

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

然后只是简单的

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

这里的诀窍是,新的路由和组件将始终出现在router-outlet之后 ,所以通过一个简单的cssselect器,我们可以显示和隐藏加载。