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

我如何显示一个载入屏幕当我改变一个路线在角度2?

120171 次浏览

更新: 3 现在我已经升级到新的路由器,如果你使用 CanDeactivate保护,@ borislemke的方法将不起作用。我退化到我的旧方法,ie:这个答案

UPDATE2 : 新路由器中的路由器事件看起来很有希望,@ borislemke答案似乎涵盖了微调器实现的主要方面,我还没有测试过,但是我推荐它。

UPDATE1: 我在 Old-Router时代写了这个答案,那时候通过 router.subscribe()只有一个事件 route-changed通知。我也觉得下面的方法负荷过重,并试图做到这一点,只使用 router.subscribe(),它 事与愿违,因为没有办法检测 canceled navigation。因此,我不得不回到冗长的方法(双重工作)。


如果你熟悉 Angular2,这就是你需要的


靴子

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

Spinner-Component (将订阅 Spinner-service 以相应地更改 active 的值)

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 this service)

定义一个可被 spinner-Component 订阅的可观察对象,以更改更改状态,并通过函数了解和设置 spinner active/inactive。

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

当前的角路由器提供导航事件。您可以订阅这些内容并相应地进行 UI 更改。请记住在其他事件中计数,如 NavigationCancelNavigationError,以便在路由器转换失败时停止您的 spinner。

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

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>

性能改进答案 : 如果您关心性能,那么有一个更好的方法,它的实现稍微有些繁琐,但是性能改进将值得额外的工作。取代使用 *ngIf有条件地显示旋转器,我们可以利用 Angular 的 NgZoneRenderer来打开/关闭旋转器,当我们改变旋转器的状态时,它将绕过 Angular 的变化检测。我发现这使得动画比使用 *ngIfasync管更加平滑。

这与我之前的回答相似,只是做了一些调整:

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

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>

为什么不使用简单的 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;
}

这里的诀窍是,新的路由和组件总是出现在 路由器插座之后,所以使用一个简单的 css 选择器,我们可以显示和隐藏加载。

您也可以使用这个 现有的解决方案。演示 在这里。看起来像是 Youtube 上的加载栏。我只是找到它,并添加到我自己的项目。

如果你有特殊的逻辑需要的 第一路线,只有你可以做到以下几点:

应用程序组件

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

我需要这个来隐藏页脚,否则它会出现在页面的顶部。另外,如果您只想要第一个页面的加载程序,您可以使用这个。