在 angular 2+ 中延迟显示导航加载器微调器

Show navigation loader spinner with delay in angular 2+

我是 angular 2+ 和 RxJS 的新手,正在努力适应 RxJS。

我在路线转换上显示负载微调器,但前提是它需要超过一定数量的时间,lats 说 160ms
我有一个加载微调器作为一个单独的组件,订阅了 ngrx 商店,所以我 show/hide 根据疼痛中的值加载微调器 (showSpinner)

在我的应用程序根组件中,我订阅了路由器更改事件,并调度了操作 (SHOW_SPINNER/HIDE_SPINNER)
那么问题来了,有没有更简单的实现方式呢?

这是我的部分代码

....

export const navigationStreamForSpinnerStatuses = {
  NAVIGATION_STARTED: 'NAVIGATION_STARTED',
  NAVIGATION_IN_PROGRESS: 'NAVIGATION_IN_PROGRESS',
  NAVIGATION_ENDED: 'NAVIGATION_ENDED'
};

.....

private navigationStartStream;
private navigationStartStreamWithDelay;
private navigationFinishStream;

constructor(private store: Store<IAppState>, private router: Router) {
  this.navigationStartStream = router.events
    .filter(event => {
      return event instanceof NavigationStart;
    })
    .map(() => navigationStreamForSpinnerStatuses.NAVIGATION_STARTED);

  this.navigationStartStreamWithDelay = this.navigationStartStream
    .delay(160)
    .map(() => navigationStreamForSpinnerStatuses.NAVIGATION_IN_PROGRESS);

  this.navigationFinishStream = router.events
    .filter(event => {
      return event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError;
    })
    .map(() => navigationStreamForSpinnerStatuses.NAVIGATION_ENDED);

  this.navigationStartStream
    .merge(this.navigationFinishStream)
    .merge(this.navigationStartStreamWithDelay)
    .pairwise()
    .subscribe([previousStatus, currentStatus] => {
      if (previousStatus !== navigationStreamForSpinnerStatuses.NAVIGATION_ENDED && currentStatus === navigationStreamForSpinnerStatuses.NAVIGATION_IN_PROGRESS) {
        this.store.dispatch({ type: StateLoaderSpinnerActionsTypes.SHOW_SPINNER });
      } else if (previousStatus === navigationStreamForSpinnerStatuses.NAVIGATION_IN_PROGRESS && currentStatus === navigationStreamForSpinnerStatuses.NAVIGATION_ENDED) {
        this.store.dispatch({ type: StateLoaderSpinnerActionsTypes.HIDE_SPINNER });
      }
    });
}

使用 takeUntil 运算符取消您的微调器计时器,如果它 returns 早于该时间。此外,使用 timer 创建一个热可观察对象,以便在经过特定时间后触发一个动作。

takeUntil Returns the values from the source observable sequence until the other observable sequence or Promise produces a value.

https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/takeuntil.md

timer Returns an observable sequence that produces a value after dueTime has elapsed and then after each period.

https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/timer.md

您可以通过直接在每个流上处理分派来简化您的逻辑。

this.navigationEnd$ = router.events
  .filter(event => event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError);

this.navigationStart$ = router.events
  .filter(event => event instanceof NavigationStart)
  .subscribe(_ => {
    Observable.timer(160)
      .takeUntil(this.navigationEnd$)
      .subscribe(_ => this.store.dispatch({ type: StateLoaderSpinnerActionsTypes.SHOW_SPINNER });
  });

this.navigationEnd$.subscribe(_ => this.store.dispatch({ type: StateLoaderSpinnerActionsTypes.HIDE_SPINNER });

所以我们所做的是监听导航的开始并启动 timer 160 毫秒。如果导航结束事件发生在计时器之前,则不会显示微调器 (takeUntil)。否则,将调度存储操作并显示微调器。无论是否显示微调器,导航完成后我们都会发送隐藏微调器操作。

我的方法基于 Http 请求,这可能不适用于所有情况,因为某些应用程序将仅使用 WebSockets 或根本不使用外部请求。但是对于大量应用程序,他们通过 HttpClient (angular 4.3+) 获取所有数据。我写了一个 HttpInterceptor 来做这个。

import { HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http'
import { Inject, Injectable, RendererFactory2 } from '@angular/core'
import { Observable, timer } from 'rxjs'
import { filter, takeUntil, tap } from 'rxjs/operators'
import { DOCUMENT } from '@angular/common'

const reqIsSpinnable = (req: HttpRequest<any>) => {
  return req.url.includes('api/')
}

@Injectable()
export class HttpSpinnerInterceptor implements HttpInterceptor {
  constructor(@Inject(DOCUMENT) private doc: HTMLDocument, private rdf: 
    RendererFactory2) { }

  // tslint:disable-next-line:no-null-keyword
  readonly rdr = this.rdf.createRenderer(null, null)

  get spinnerElement() {
    return this.doc.querySelector('#core-spin')
  }

  startSpin() {
    this.rdr.setStyle(this.spinnerElement, 'display', 'block')
  }

  closeSpin() {
    this.rdr.setStyle(this.spinnerElement, 'display', 'none')
  }

  intercept(req: HttpRequest<any>, next: HttpHandler):
    Observable<any> {
    const responseTimer$ = next.handle(req).pipe(filter(e => e instanceof HttpResponse))
    timer(120).pipe(takeUntil(responseTimer$)).subscribe(() => this.startSpin())

    return next.handle(req).pipe(tap(evt => {
      if (reqIsSpinnable(req) && evt instanceof HttpResponse) {
        this.closeSpin()
      }
    }))

} }