Html 在 Angular 2 中的路线之间导航时显示加载屏幕
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/37069609/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Show loading screen when navigating between routes in Angular 2
提问by Lucas
How do I show a loading screen when I change a route in Angular 2?
在 Angular 2 中更改路线时如何显示加载屏幕?
回答by borislemke
The current Angular Router provides Navigation Events. You can subscribe to these and make UI changes accordingly. Remember to count in other Events such as NavigationCancel
and NavigationError
to stop your spinner in case router transitions fail.
当前的 Angular Router 提供导航事件。您可以订阅这些并相应地更改 UI。记住要计算其他事件,例如NavigationCancel
并NavigationError
在路由器转换失败时停止您的微调器。
app.component.ts- your root component
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- your root view
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>
Performance Improved Answer: If you care about performance there is a better method, it is slightly more tedious to implement but the performance improvement will be worth the extra work. Instead of using *ngIf
to conditionally show the spinner, we could leverage Angular's NgZone
and Renderer
to switch on / off the spinner which will bypass Angular's change detection when we change the spinner's state. I found this to make the animation smoother compared to using *ngIf
or an async
pipe.
性能改进答案:如果您关心性能,则有更好的方法,实施起来稍微有些乏味,但性能改进值得额外的工作。*ngIf
我们可以利用 AngularNgZone
并Renderer
打开/关闭微调器,而不是有条件地显示微调器,这将在我们更改微调器状态时绕过 Angular 的更改检测。我发现与使用*ngIf
或async
管道相比,这可以使动画更流畅。
This is similar to my previous answer with some tweaks:
这类似于我之前的答案,但有一些调整:
app.component.ts- your root component
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- your root view
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>
回答by Ankit Singh
UPDATE:3Now that I have upgraded to new Router, @borislemke's approach will not work if you use CanDeactivate
guard. I'm degrading to my old method, ie:
this answer
更新:3现在我已经升级到新的路由器,如果你使用守卫,@borislemke的方法将不起作用CanDeactivate
。我正在贬低我的旧方法,ie:
这个答案
UPDATE2: Router events in new-router look promising and the answerby @borislemkeseems to cover the main aspect of spinner implementation, I havent't tested it but I recommend it.
UPDATE2:在新的路由器看好和路由器事件,答案由@borislemke似乎涵盖微调实施的主要方面,我havent't测试,但我推荐它。
UPDATE1:I wrote this answer in the era of Old-Router
, when there used to be only one event route-changed
notified via router.subscribe()
. I also felt overload of the below approach and tried to do it using only router.subscribe()
, and it backfiredbecause there was no way to detect canceled navigation
. So I had to revert back to lengthy approach(double work).
UPDATE1:我在 时代写了这个答案Old-Router
,当时只有一个事件route-changed
通过router.subscribe()
. 我也觉得超负荷下面的方法,并试图仅用做到这一点router.subscribe()
,它事与愿违,因为没有办法检测canceled navigation
。所以我不得不回到冗长的方法(双重工作)。
If you know your way around in Angular2, this is what you'll need
如果您熟悉 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]);
Root Component- (MyApp)
根组件-(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(will subscribe to Spinner-service to change the value of active accordingly)
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-Service(引导此服务)
Define an observable to be subscribed by spinner-component to change the status on change, and function to know and set the spinner active/inactive.
定义一个由 spinner-component 订阅的 observable 以更改更改状态,以及了解和设置微调器活动/不活动的功能。
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;
}
}
All Other Routes' Components
所有其他路由的组件
(sample):
(样本):
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();
}
}
回答by Milad
Why not just using simple css :
为什么不只使用简单的 css :
<router-outlet></router-outlet>
<div class="loading"></div>
And in your styles :
在你的风格中:
div.loading{
height: 100px;
background-color: red;
display: none;
}
router-outlet + div.loading{
display: block;
}
Or even we can do this for the first answer:
或者甚至我们可以为第一个答案执行此操作:
<router-outlet></router-outlet>
<spinner-component></spinner-component>
And then simply just
然后只是
spinner-component{
display:none;
}
router-outlet + spinner-component{
display: block;
}
The trick here is, the new routes and components will always appear after router-outlet, so with a simple css selector we can show and hide the loading.
这里的技巧是,新的路由和组件将始终出现在router-outlet 之后,因此我们可以使用简单的 css 选择器来显示和隐藏加载。
回答by Simon_Weaver
If you have special logic required for the firstroute only you can do the following:
如果您只有第一条路线需要特殊逻辑,您可以执行以下操作:
AppComponent
应用组件
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');
});
I had a need for this to hide my page footer, which was otherwise appearing at the top of the page. Also if you only want a loader for the first page you can use this.
我需要这个来隐藏我的页脚,否则它会出现在页面顶部。此外,如果您只想要第一页的加载程序,您可以使用它。
回答by Jonatan Dragon
You could also use this existing solution. The demo is here. It looks like youtube loading bar. I just found it and added it to my own project.
您也可以使用这个现有的解决方案。演示在这里。它看起来像 youtube 加载栏。我刚刚找到它并将其添加到我自己的项目中。