我想使用 RxJS(版本 6)实现加载指示。在异步数据调用完成之前,加载指示器(微调器)将显示在组件中。我有一些规则要执行(这些规则是否正确可能是另一个问题,也许可以发表评论):
我正在一个 Angular 项目中实现它,但我相信,这不是特定于 Angular 的。
我找到了这个拼图的一些碎片,但我需要帮助将它们组装在一起。
在这个 SO answer 中有一个操作符的实现,它延迟了加载指示器的显示。
本文描述了一个很好但不完整的 Angular 实现。
在这篇 Medium 文章中描述了在最短的时间内显示加载指示器。
kos*_*kos 18
首先,这是一个很好的问题,卢卡斯!
前言:虽然还有其他方法可以实现您的要求,但我只是想让我的答案更像是一个详细的分步教程。看看 Brandon 的惊人解决方案,就在这个解决方案的正下方。
为方便起见,让我们假设我们有一个方法来执行请求并返回一个 Observable 字符串消息:
const makeARequest: () => Observable<{ msg: string }>;
Run Code Online (Sandbox Code Playgroud)
现在我们可以声明我们的 Observables 来保存结果:
// Our result will be either a string message or an error
const result$: Observable<{ msg: string } | { error: string }>;
Run Code Online (Sandbox Code Playgroud)
和加载指示:
// This stream will control a loading indicator visibility
// if we get a true on the stream -- we'll show a loading indicator
// on false -- we'll hide it
const loadingIndicator$: Observable<boolean>;
Run Code Online (Sandbox Code Playgroud)
现在,解决#1
如果数据早于 1 秒成功到达,则不应显示任何指示符(并且数据应正常呈现)
我们可以设置一个1 秒的计时器并将该计时器事件转换为一个true值,这意味着显示加载指示器。takeUntil将确保如果 aresult$在 1 秒之前出现 - 我们不会显示加载指示器:
const showLoadingIndicator$ = timer(1000).pipe(
mapTo(true), // turn the value into `true`, meaning loading is shown
takeUntil(result$) // emit only if result$ wont emit before 1s
);
Run Code Online (Sandbox Code Playgroud)
#2
如果调用失败早于 1 秒,则不应显示任何指示符(并且应呈现错误消息)
虽然第一部分将由 #1 解决,但为了显示错误消息,我们需要从源流中捕获错误并将其转换为某种{ error: 'Oops' }. 一个catchError运营商将让我们做到这一点:
result$ = makeARequest().pipe(
catchError(() => {
return of({ error: 'Oops' });
})
)
Run Code Online (Sandbox Code Playgroud)
您可能已经注意到我们result$在两个地方使用了。这意味着我们将对同一个请求 Observable 有两个订阅,这将发出两个请求,这不是我们想要的。为了解决这个问题,我们可以简单地在订阅者之间共享这个 observable:
result$ = makeARequest().pipe(
catchError(() => { // an error from the request will be handled here
return of({ error: 'Oops' });
}),
share()
)
Run Code Online (Sandbox Code Playgroud)
#3
如果数据迟于 1 秒到达,则应显示至少 1 秒的指示器(为防止闪烁的微调器,数据应在之后呈现)
首先,我们有办法把负载指示上,虽然我们目前还没有把它关闭。让我们使用result$流上的事件作为我们可以隐藏加载指示器的通知。一旦我们收到结果——我们可以隐藏指标:
// this we'll use as an off switch:
result$.pipe( mapTo(false) )
Run Code Online (Sandbox Code Playgroud)
所以我们可以merge进行通断切换:
const showLoadingIndicator$ = merge(
// ON in 1second
timer(1000).pipe( mapTo(true), takeUntil(result$) ),
// OFF once we receive a result
result$.pipe( mapTo(false) )
)
Run Code Online (Sandbox Code Playgroud)
现在我们已经打开和关闭加载指示器,尽管我们需要摆脱闪烁的加载指示器并至少显示 1 秒。我想,最简单的方法是将关闭开关的最新值和 2 秒计时器结合起来:
const showLoadingIndicator$ = merge(
// ON in 1second
timer(1000).pipe( mapTo(true), takeUntil(result$) ),
// OFF once we receive a result, yet at least in 2s
combineLatest(result$, timer(2000)).pipe( mapTo(false) )
)
Run Code Online (Sandbox Code Playgroud)
注意:如果结果是在 2 秒之前收到的,这种方法可能会给我们一个2 秒的冗余关闭开关。我们稍后再处理。
#4
如果呼叫在 1 秒后失败,则指示器应显示至少 1 秒
我们对#3 的解决方案已经有一个反闪存代码,在#2 中我们已经处理了流抛出错误的情况,所以我们在这里很好。
#5
如果通话时间超过 10 秒,则应取消通话(并显示错误消息)
为了帮助我们取消长时间运行的请求,我们有一个超时操作符:如果源 observable 在给定时间内没有发出值,它将抛出一个错误
result$ = makeARequest().pipe(
timeout(10000), // 10 seconds timeout for the result to come
catchError(() => { // an error from the request or timeout will be handled here
return of({ error: 'Oops' });
}),
share()
)
Run Code Online (Sandbox Code Playgroud)
我们快完成了,只剩下一点点改进。让我们showLoadingIndicator$用一个false值开始我们的流,表明我们没有在开始时显示加载器。并使用distinctUntilChanged,省略掉,以关闭开关,我们可以在#3,由于我们的方法得到。
总而言之,这是我们取得的成就:
const { fromEvent, timer, combineLatest, merge, throwError, of } = rxjs;
const { timeout, share, catchError, mapTo, takeUntil, startWith, distinctUntilChanged, switchMap } = rxjs.operators;
function startLoading(delayTime, shouldError){
console.log('====');
const result$ = makeARequest(delayTime, shouldError).pipe(
timeout(10000), // 10 seconds timeout for the result to come
catchError(() => { // an error from the request or timeout will be handled here
return of({ error: 'Oops' });
}),
share()
);
const showLoadingIndicator$ = merge(
// ON in 1second
timer(1000).pipe( mapTo(true), takeUntil(result$) ),
// OFF once we receive a result, yet at least in 2s
combineLatest(result$, timer(2000)).pipe( mapTo(false) )
)
.pipe(
startWith(false),
distinctUntilChanged()
);
result$.subscribe((result)=>{
if (result.error) { console.log('Error: ', result.error); }
if (result.msg) { console.log('Result: ', result.msg); }
});
showLoadingIndicator$.subscribe(isLoading =>{
console.log(isLoading ? '? loading' : ' free');
});
}
function makeARequest(delayTime, shouldError){
return timer(delayTime).pipe(switchMap(()=>{
return shouldError
? throwError('X')
: of({ msg: 'awesome' });
}))
}Run Code Online (Sandbox Code Playgroud)
<b>Fine requests</b>
<button
onclick="startLoading(500)"
>500ms</button>
<button
onclick="startLoading(1500)"
>1500ms</button>
<button
onclick="startLoading(3000)"
>3000ms</button>
<button
onclick="startLoading(11000)"
>11000ms</button>
<b>Error requests</b>
<button
onclick="startLoading(500, true)"
>Err 500ms</button>
<button
onclick="startLoading(1500, true)"
>Err 1500ms</button>
<button
onclick="startLoading(3000, true)"
>Err 3000ms</button>
<script src="https://unpkg.com/rxjs@6.5.2/bundles/rxjs.umd.min.js"></script>Run Code Online (Sandbox Code Playgroud)
希望这可以帮助
| 归档时间: |
|
| 查看次数: |
4347 次 |
| 最近记录: |