Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RXJS - start a timer only when idle?

I'm using a stream which is throttled when I scroll the window.
While throttling (as long as scrolling), it emits values to the console.

However , when stream is idle (user is not scrolling the window) - I want a timer to kick in. However - if the user starts scrolling again - I don't want that timer to emit values.

Currently I'm doing this :

  const observable = Rx.Observable.fromEvent(window, 'scroll');

  const subscriber = observable
      .throttleTime(300 )
      .map(() => 'throttle')
      .merge(Rx.Observable.interval(1000).map(() => 'tick') )
      .subscribe(
          (x) => {
            console.log('Next: event!', x);
          },
          (err) => {
            console.log('Error: %s', err);
          },
          () => {
            console.log('Completed');
          });

The problem is that , while scrolling - I see both "throttle" AND "tick" ( I should only see "throttle")

Think of this from another POV. A job always has to run. If I scroll - that throttled scroll - should invoke the job. If I don't scroll - a timer should kick in and start doing the job . (and stops if user start scrolling again).

Question:
How can I start a timer after an idle time of not scrolling ?

PLNKR

like image 984
Royi Namir Avatar asked Apr 20 '17 17:04

Royi Namir


1 Answers

You can use debounceTime to detect periods without scrolling.

const scroll = Rx.Observable.fromEvent(window, 'scroll')
  .throttleTime(300)
  .mapTo(false);
const noscroll = Rx.Observable.fromEvent(window, 'scroll')
  .startWith(0) // init with no scroll.
  .debounceTime(300) // detect no scroll after 300 ms.
  .mapTo(true);
scroll.merge(noscroll)
  .switchMap(e => e ? Rx.Observable.interval(1000).mapTo("Tick!") : Rx.Observable.of("Scroll!"))  
  // start the interval if there was no scroll. Stop the interval if there was a scroll.
  .subscribe(updateTimer)

Another problem with your code is using merge that will keep both sources subscribed, instead i use switchMap (a sibling of mergeMap) that will subscribe to the inner observable each time a new event is emitted, but also unsubscribe the previous inner source if another event is emitted from the source.

Re: "another POV" part of the question: You can replace Rx.Observable.interval(1000) in switchMap with the job. Scrolling will cancel/unsubscribe the job (as empty is emitted), if there is no more scrolling, the job will start again.

Live demo

like image 194
Dorus Avatar answered Sep 27 '22 17:09

Dorus