【问题标题】:Show loading screen when navigating between routes in Angular 2在 Angular 2 中的路由之间导航时显示加载屏幕
【发布时间】:2016-09-01 08:43:34
【问题描述】:

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

【问题讨论】:

标签: html css angular angular2-routing


【解决方案1】:

当前的 Angular 路由器提供导航事件。您可以订阅这些并相应地更改 UI。请记住计入其他事件,例如 NavigationCancelNavigationError,以在路由器转换失败时停止微调器。

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 的 NgZoneRenderer 来打开/关闭微调器,而不是使用 *ngIf 有条件地显示微调器,这将在我们更改微调器状态时绕过 Angular 的更改检测。与使用 *ngIfasync 管道相比,我发现这可以使动画更流畅。

这与我之前的回答类似,但有一些调整:

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 navigationit triggering the spinner 的控制,然后无法阻止它。 navigationInterceptor 似乎是一个解决方案,但我不确定是否还有其他问题正在等待中断。如果与async requests 混合使用,我认为会再次引入问题。
  • 也许这在某些时候有效?它现在不适用于 Angular 2.4.8。页面是同步的。在整个页面/父组件呈现之前,Spinner 不会被呈现,并且在 NavigationEnd。这破坏了微调器的意义
  • 使用不透明度并不是一个好的选择。从视觉上看,这很好,但在 Chrome 中,如果加载图像/图标位于按钮或文本的顶部,则无法访问该按钮。我将其更改为使用displaynoneinline
  • 我喜欢这种方法,干得好!但是我有一个问题,我不明白为什么,如果我不在 NavigationEnd 上切换动画,我可以看到微调器正在加载,但是如果我切换到 false,路线变化如此之快,以至于我什至看不到任何动画 :( 我试过了甚至网络节流以减慢连接速度,但它仍然保持不变:(根本没有加载。你能给我任何建议吗?我通过在加载元素上添加和删除类来控制动画。谢谢
  • 复制您的代码'md-spinner' is not a known element: 时出现此错误。我对 Angular 很陌生。你能告诉我可能是什么错误吗?
【解决方案2】:

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

UPDATE2:new-router 中的路由器事件看起来很有希望,@borislemkethe 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();
  }
}

【讨论】:

  • see this 也是。我不知道默认情况下动画支持多少角度。
  • 你能帮我写一下微调器的服务吗?我可以自己在 CSS 中制作动画等等,但如果您能帮助我提供这项服务,那就太好了。
  • see this implemetation 也一样,但不完全一样
  • 我已经为spinner-service 添加了一些代码,现在你只需要其他部分就可以了。请记住它是为angular2-rc-1
【解决方案3】:

为什么不只使用简单的 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 选择器来显示和隐藏加载。

【讨论】:

  • 不允许我们将值从组件传递给父组件,所以隐藏加载 div 会有点复杂。
  • 此外,如果您的应用程序有很多路由更改以每次都立即看到微调器,这可能会非常烦人。最好使用 RxJs 并设置一个去抖动的计时器,这样它只会在短暂的延迟后出现。
【解决方案4】:

如果您只有 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');
                   });

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

【讨论】:

    【解决方案5】:

    您也可以使用此existing solution。演示is here。它看起来像 youtube 加载栏。我刚刚找到它并将其添加到我自己的项目中。

    【讨论】:

    • 它对解析器有帮助吗?我的问题是,当解析器带回数据时,我无法在任何地方显示微调器,因为尚未调用实际的目标组件 ngOninit !我的想法是在 ngOnInit 中显示微调器,并在路由订阅返回解析数据后隐藏它
    猜你喜欢
    • 2022-01-21
    • 2017-05-14
    • 2018-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-05
    相关资源
    最近更新 更多