在 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()
}
}))
}
}
我是 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()
}
}))
} }