8

Take for example:

 this.http.get('/getdata').pipe(delay(2000))

I would like this request to take a minimum of 2s to complete, but not any longer than it takes for the request to complete.

In other words:

  1. if the request takes 1s to complete, I want the observable to complete in 2s.

  2. if the request takes 3s to complete, I want the observable to complete in 3s NOT 5s.

Is there some other pipe other than delay() that can achieve this that I don't know about or is there a way to build a custom pipe for this if necessary?

The use case is to show a loader, however if the request completes too fast it doesnt look good when the loader just "flashes" for a split second

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
parliament
  • 21,544
  • 38
  • 148
  • 238
  • Consider a better solution from a UX perspective. Only show the loader after a short duration if the request has not yet returned. That means that if the request returns fast, no loader is necessary at all. – GregL Jan 03 '19 at 22:39
  • 1
    @GregL that just *moves* the same problem. If the request takes only slightly longer than the short duration, the loader appears then disappears again immediately. – jonrsharpe Jan 03 '19 at 22:41
  • 1
    Why not move this logic to the *loader*? It gets shown when the request starts, then told to go when the request ends, and it's up to *it* to decide whether it needs to pause before disappearing to avoid the flickering effect. It seems weird to deliberately delay the actual data and processing, rather than just the display. – jonrsharpe Jan 03 '19 at 22:43
  • @jonrsharpe You are always going to have a case where the loader is shown inconveniently just before the response returns. It's about reducing the likelihood of that happening by omitting the spinner for users where a response is returned prior to the user thinking the UI is nonresponsive (which is the purpose of a loader, to show you that something is happening). – GregL Jan 03 '19 at 22:48
  • 1
    That said, there is valid use cases for deliberately delaying updating the UI, even if the response is fast. There are certain operations we have been conditioned to believe take some noticeable period of time. If they instead appear to happen instantly, people distrust the system and believe it was too fast to be real. – GregL Jan 03 '19 at 22:50

2 Answers2

7

To answer the question as asked, you could simply use combineLatest() to combine a timer(2000) observable and the request observable, then just ignore the result from the timer observable. It works because combineLatest waits until all observables have emitted at least one value before emitting one itself.

combineLatest(this.http.get('/getdata'), timer(2000), x => x)
GregL
  • 37,147
  • 8
  • 62
  • 67
1

Thanks to GregL, I updated this to just use a forkJoin. This will get the latest value of the streams. But if you want to check it on every emmission, you can replace forkJoin with combineLatest and that will work too. In my working example:

    this.ibanSubscription = forkJoin({
        iban: this.ibantobicService.getById(Iban),
        timer: timer(1000) //so now the ajax call will take at least 1 second 
        }
    ).pipe(
        map( (stream: any) => <BicModel>stream.iban),
        switchMap( (bic: BicModel) => of(this.processIbanData(bic))),
        catchError((error: any) => of(this.messageList.handleError(error))),
        finalize(() => this.loadIbanToBicFinalize())
   ).subscribe();
chriscrossweb
  • 338
  • 1
  • 5
  • 23