Показывать навигацию загрузчика с задержкой на угол 2+
Я новичок в angular 2+ и RxJS, пытаюсь привыкнуть к RxJS.
Я показываю счетчик нагрузки на переходах по маршруту, но только если это занимает больше определенного времени, лат, скажем, 160 мс
У меня есть загрузочный счетчик как отдельный компонент с подпиской на хранилище ngrx, поэтому я показываю / скрываю загрузочный счетчик на основе значения в болячке (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 });
}
});
}
2 ответа
Используйте takeUntil
оператор, чтобы отменить таймер спиннера, если он возвращается раньше времени. Кроме того, создайте горячую наблюдаемую, используя timer
инициировать действие по истечении определенного времени.
takeUntil
Возвращает значения из исходной наблюдаемой последовательности, пока другая наблюдаемая последовательность или Promise не получит значение.
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/takeuntil.md
timer
Возвращает наблюдаемую последовательность, которая выдает значение после истечения срока и времени после каждого периода.
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 (угловой 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()
}
}))
}}