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

Luc*_*emy 116 html css angular2-routing angular

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

bor*_*mke 184

当前的角度路由器提供导航事件.您可以订阅这些并相应地进行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) {
    router.events.subscribe(this.navigationInterceptor)
  }

  // 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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

性能改进答案:如果你关心性能,有一个更好的方法,实现起来稍微繁琐,但性能提升值得额外的工作.而不是使用的*ngIf便有条件地显示微调,我们可以利用角的NgZoneRenderer开启/关闭,这将绕过角的变化检测,当我们改变微调的状态的微调.我发现这使得动画与使用*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'
      )
    })
  }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这种方法,干得好!但是我有一个问题,我不明白为什么,如果我不在 NavigationEnd 上切换动画,我可以看到微调器正在加载,但是如果我切换到 false,路线变化如此之快,以至于我什至看不到任何动画:( 我试过甚至通过网络节流来减慢连接速度,但它仍然保持不变:(根本没有加载。你能给我任何建议吗?我通过在加载元素上添加和删除类来控制动画。谢谢 (2认同)

Ank*_*ngh 37

更新:3现在我已经升级到新的路由器,如果你使用后卫,@ borislemke的方法将不起作用CanDeactivate.我的贬低我的旧方法,ie:这个答案

UPDATE2:在新的路由器路由器事件看好和答案通过@borislemke似乎涵盖微调实施的主要方面,我havent't测试,但我推荐它.

更新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]);
Run Code Online (Sandbox Code Playgroud)

根组件 - (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 { }
Run Code Online (Sandbox Code Playgroud)

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

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;
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

Spinner-Service(引导此服务)

定义要由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;
  }
}
Run Code Online (Sandbox Code Playgroud)

所有其他路线的组件

(样品):

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();
  }
}
Run Code Online (Sandbox Code Playgroud)


Mil*_*lad 10

为什么不使用简单的CSS:

<router-outlet></router-outlet>
<div class="loading"></div>
Run Code Online (Sandbox Code Playgroud)

在你的风格:

div.loading{
    height: 100px;
    background-color: red;
    display: none;
}
router-outlet + div.loading{
    display: block;
}
Run Code Online (Sandbox Code Playgroud)

或者甚至我们可以为第一个答案做到这一点:

<router-outlet></router-outlet>
<spinner-component></spinner-component>
Run Code Online (Sandbox Code Playgroud)

然后就是这么简单

spinner-component{
   display:none;
}
router-outlet + spinner-component{
    display: block;
}
Run Code Online (Sandbox Code Playgroud)

这里的诀窍是,新路由和组件将始终出现在路由器出口之后,因此使用简单的css选择器我们可以显示和隐藏加载.

  • 此外,如果您的应用程序有很多路由更改以立即查看微调器,这可能会非常烦人。最好使用 RxJs 并设置去抖动计时器,这样它只会在短暂延迟后出现。 (2认同)