Показывать экран загрузки при навигации между маршрутами в Angular 2



Как показать загрузочный экран при изменении маршрута в Angular 2?

554   5  

5 ответов:

текущий угловой маршрутизатор обеспечивает события навигации. Вы можете подписаться на них и внести соответствующие изменения в пользовательский интерфейс. Не забывайте считать в других событиях, таких как NavigationCancel и NavigationError чтобы остановить ваш счетчик в случае сбоя переходов маршрутизатора.

приложение.деталь.ТС - корневой компонент

...
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
    }
  }
}

приложение.деталь.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>

Производительность Улучшена Ответ: если вы заботитесь о производительность есть лучший метод, его немного более утомительно реализовать, но улучшение производительности будет стоить дополнительной работы. Вместо того, чтобы использовать *ngIf чтобы условно показать прядильщик, мы могли бы использовать угловойNgZone и Renderer чтобы включить / выключить счетчик, который будет обходить обнаружение изменения углового, когда мы меняем состояние счетчика. Я нашел это, чтобы сделать анимацию более гладкой по сравнению с использованием *ngIf или async трубы.

это похоже на мой предыдущий ответ с некоторыми хитростями:

приложение.деталь.ТС - корневой компонент

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

приложение.деталь.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>

обновление:3 теперь, когда я обновился до нового маршрутизатора,@borislemke'не будет работать, если вы используете CanDeactivate гвардии. Я унижаю свой старый метод,ie: ответ

обновление 2: события маршрутизатора в new-router выглядят многообещающе и ответ by @borislemke кажется, чтобы охватить основной аспект реализации spinner, я не тестировал его, но я рекомендую оно.

UPDATE1: я написал этот ответ в эпоху Old-Router, когда было только одно событие route-changed уведомлен router.subscribe(). Я также почувствовал перегрузку ниже подхода и попытался сделать это, используя только router.subscribe(), и backfired потому что не было никакого способа, чтобы обнаружить canceled navigation. Поэтому мне пришлось вернуться к длительному подходу (двойная работа).


если вы знаете свой путь вокруг в Angular2, это то, что вы будете нужно


Boot.ТС

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-сервис, чтобы изменить значение 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 этой службы)

определите наблюдаемый, который будет подписан 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();
  }
}

Почему бы просто не использовать простой 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;
}

хитрость здесь в том, что новые маршруты и компоненты всегда будут появляться после маршрутизатор-выход, так что с помощью простого селектора css мы можем показать и скрыть загрузку.

Если у вас есть специальная логика, необходимая для первый маршрут только вы можете сделать следующее:

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

Мне нужно было это, чтобы скрыть мой нижний колонтитул страницы, который в противном случае появлялся в верхней части страницы. Также, если вы хотите только загрузчик для первой страницы, вы можете использовать это.

вы также можете использовать этот существующее решение. Демо здесь. Это похоже на панель загрузки youtube. Я просто нашел его и добавил его в свой проект.

Comments

    Ничего не найдено.