首页 文章

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

提问于
浏览
103

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

5 回答

  • 0

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

    Performance Improved Answer :如果您关心性能,那么有一种更好的方法,实施起来稍微繁琐,但性能提升值得额外工作 . 我们可以利用Angular的 NgZoneRenderer 来打开/关闭将绕过Angular 's change detection when we change the spinner'状态的微调器,而不是使用 *ngIf 来有条件地显示微调器 . 与使用 *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((event: RouterEvent) => {
          this._navigationInterceptor(event)
        })
      }
    
      // 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>
    
  • 2

    UPDATE:3 现在我已经升级到新的路由器,如果使用 CanDeactivate guard, @borislemke 的方法将无效 . 我贬低我的旧方法, ie: 这个答案

    UPDATE2 :新路由器中的路由器事件看起来很有希望the answer@borislemke 似乎涵盖了微调器实现的主要方面,我还没有测试它,但我推荐它 .

    UPDATE1: 我在 Old-Router 的时代写了这个答案,当时曾经只有一个事件 route-changed 通过 router.subscribe() 通知 . 我也觉得下面的方法超载,并试图只使用 router.subscribe() ,它 backfired ,因为没有办法检测 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]);
    

    Root Component- (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以相应地更改活动的值)

    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以更改更改时的状态,并定义用于了解和设置微调器活动/非活动的函数 .

    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

    (样品):

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

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

  • 38

    如果您只有第一条路线需要特殊逻辑,则可以执行以下操作:

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

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

  • 168

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

相关问题