在Angular 2中的路线之间导航时显示加载屏幕
当我在Angular 2中更改路线时,如何显示加载屏幕?
目前的Angular路由器提供导航事件。 您可以订阅这些内容并相应地进行UI更改。 请记住在其他事件(如NavigationCancel
和NavigationError
计数以防止路由器转换失败时的微调。
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的NgZone
和Renderer
来打开/closuresSpinner,当我们改变Spinner的状态时,它将绕过Angular的变化检测,而不是使用*ngIf
来有条件地显示Spinner。 我发现这使得animation比使用*ngIf
或async
pipe道更平滑。
这与我之前的回答有些类似:
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 navigation
而router.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器,我们可以显示和隐藏加载。