【发布时间】:2016-09-01 08:43:34
【问题描述】:
当我在 Angular 2 中更改路线时如何显示加载屏幕?
【问题讨论】:
-
上面链接的问题(AndrewL64 的评论)是关于 AngularJS,而不是“Angular”(2+)。
标签: html css angular angular2-routing
当我在 Angular 2 中更改路线时如何显示加载屏幕?
【问题讨论】:
标签: html css angular angular2-routing
当前的 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) {
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 - 你的根视图
<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 来打开/关闭微调器,而不是使用 *ngIf 有条件地显示微调器,这将在我们更改微调器状态时绕过 Angular 的更改检测。与使用 *ngIf 或 async 管道相比,我发现这可以使动画更流畅。
这与我之前的回答类似,但有一些调整:
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(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 - 你的根视图
<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 和it triggering the spinner 的控制,然后无法阻止它。 navigationInterceptor 似乎是一个解决方案,但我不确定是否还有其他问题正在等待中断。如果与async requests 混合使用,我认为会再次引入问题。
display 或none 或inline。
'md-spinner' is not a known element: 时出现此错误。我对 Angular 很陌生。你能告诉我可能是什么错误吗?
更新:3 现在我已经升级到新路由器,如果您使用 CanDeactivate 保护,@borislemke 的方法将不起作用。我正在退化为我的旧方法,ie: 这个答案
UPDATE2:new-router 中的路由器事件看起来很有希望,@borislemke 的the answer 似乎涵盖了微调器实现的主要方面,我还没有测试过但我推荐它。
UPDATE1: 我在Old-Router 时代写了这个答案,当时只有一个事件route-changed 通过router.subscribe() 通知。我也觉得下面的方法过载,并尝试只使用router.subscribe(),它适得其反,因为没有办法检测到canceled navigation。所以我不得不回到冗长的方法(双重工作)。
如果您熟悉 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 { }
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(引导此服务)
定义一个由 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();
}
}
【讨论】:
spinner-service 添加了一些代码,现在你只需要其他部分就可以了。请记住它是为angular2-rc-1
为什么不只使用简单的 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 之后,所以我们可以通过一个简单的 css 选择器来显示和隐藏加载。
【讨论】:
如果您只有 first 路由需要特殊逻辑,您可以执行以下操作:
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');
});
我需要这个来隐藏我的页脚,否则它会出现在页面顶部。此外,如果您只想要第一页的加载器,您可以使用它。
【讨论】:
您也可以使用此existing solution。演示is here。它看起来像 youtube 加载栏。我刚刚找到它并将其添加到我自己的项目中。
【讨论】: