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

问题描述 投票:0回答:5

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

html css angular angular2-routing
5个回答
220
投票

当前的 Angular Router 提供导航事件。您可以订阅这些内容并相应地更改 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 的

*ngIf
NgZone
来打开/关闭微调器,而不是使用
Renderer
有条件地显示微调器,当我们更改微调器的状态时,这将绕过 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>

42
投票

更新:3现在我已经升级到新的路由器,如果您使用CanDeactivate守卫,

@borislemke
的方法将不起作用。我正在使用我的旧方法,
ie:
这个答案

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

更新1:我在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(引导此服务)

定义一个由微调器组件订阅的可观察量,以更改更改时的状态,并用于了解和设置微调器活动/不活动。

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

13
投票
为什么不只使用简单的 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 选择器,我们可以显示和隐藏加载。


2
投票
如果您有

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'); });
我需要这个来隐藏我的页脚,否则它会出现在页面顶部。另外,如果您只想要第一页的加载器,您可以使用它。


0
投票

2024 年接受答案的补充

接受的答案就像一个魅力,但需要一点不同的打字才能在新的 Angular 版本中工作:

private destroyRef = inject(DestroyRef); constructor(private router: Router) { this.router.events .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((e) => { this.navigationInterceptor(e.type); }); } private navigationInterceptor(eventType: EventType): void { if (eventType === EventType.NavigationStart) { this.isNavigating = true; } if (eventType === EventType.NavigationEnd) { this.isNavigating = false; } // Set loading state to false in both of the below events to hide the spinner in case a request fails if (eventType === EventType.NavigationCancel) { this.isNavigating = false; } if (eventType === EventType.NavigationError) { this.isNavigating = false; } }
请注意,我添加了一个带有对 RxJs 位的销毁引用的管道,这应该是处理 RxJs 订阅时常见的事情。这种带有 

takeUntilDestroyed()

 的特殊语法从 
Angular 16 开始工作。

© www.soinside.com 2019 - 2024. All rights reserved.