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
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
Share Improve this question edited Apr 23, 2017 at 7:20 Royi Namir asked Apr 20, 2017 at 17:38 Royi NamirRoyi Namir 149k144 gold badges493 silver badges831 bronze badges 2- 2 This is how to make a good question on SO. +1 – kind user Commented Apr 20, 2017 at 17:38
-
Re your other ment: You could solve this using the trick there using
exhaustMap
+debounceTime
to detect no scroll. What i did here withswitchMap
works because we do nothing during the burst of events and the work happens when the stream is idle. The other way around,switchMap
would restart the job(timer) each time a new scroll event is emitted. – Dorus Commented Apr 20, 2017 at 20:13
2 Answers
Reset to default 3You 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
I'd do it like this:
const scroll$ = Rx.Observable.fromEvent(window, 'scroll')
.throttleTime(300 /* ms */)
.publish();
scroll$.connect();
const subscriber = scroll$
.map(() => 'throttle')
.race(Rx.Observable.interval(1000).map(() => 'tick'))
.take(1)
.repeat()
.subscribe(
(x) => {
console.log('Next: event!', x);
},
(err) => {
console.log('Error: %s', err);
},
() => {
console.log('Completed');
});
This uses the race()
operator to subscribe only to the Observable that emits first which is the 1s interval
or the scroll event. Right after that I want to start this again with another interval so I use take(1).repeat()
.
I also had to turn the scroll$
Observable into a hot Observable to keep the throttleTime()
running among the repeated subscriptions.
Your updated demo: https://plnkr.co/edit/sWzSm32uoOQ1hOKigo4s?p=preview