See a practical example of reactive programming in JavaScript and the DOM. Learn how to detect double clicks with a few operators in RxJS. We will use .bufferWhen
to accumulate events into an array to determine if a double click occurred.
I downloaded RxJS v4 and the double-click doesn't register. Switching to v2.3.22 - using https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.3.22/rx.all.js - the example started work.
Using v4 you need to use debounce instead of throttle
Thanks!
Cool, I'm not the first to stumble on the throttle/debounce thing. Thanks for already having it answered here :)
+1 for thanks :)
How this code can be modified, so the doubleClick stream emits rightaway and not after 250ms delay?
Remember to use debounce()
instead of throttle()
-- see the code examples on the page.
Just subscribed to pro and not cool to see out of date tutorials. Using 5-beta I expected something not to work, but this tutorial running on 2 is ancient. The JSBin itself references 4 and doesn't even work.
Thanks Dominic for reporting this. We fixed the JSBin example by changing the use of throttle() to delay(), because in older versions of RxJS, throttle used to have some delaying behavior too.
I spent quite some time trying to figure out how to do the same thing with the latest version of RxJS (5.0.0-beta.10) so I'd like to share here how I got it working:
const single$ = Rx.Observable.fromEvent(button, 'click');
single$
.bufferWhen(() => single$.debounceTime(250))
.map(list => list.length)
.filter(length => length >= 2)
.subscribe(totalClicks => {
console.log(`multi clicks total: ${totalClicks}`);
});
Thanks for this intro course Andre and please let me know if there's something wrong with this implementation or if there's a better way to do the same thing.
Here is the version which works on rxjs 5.x:
const timeout = 250;
const clicks$ = Rx.Observable.fromEvent(button, "click");
const doubleClicks$ = clicks$
.map(() => new Date())
.bufferCount(2, 1)
.map(([prev, next]) => (next - prev) < timeout)
.scan((prev, next) => !prev && next, false)
.filter(v => v)
Would work with that code too:
.bufferWhen(() => clickStream.debounceTime(250))
.filter(arr => arr.length === 2);
That's what I ended up with in v5 too. I'm still bothered by the fact that the 250 ms delay applies even after the second click. Native double click events fire at mouse up, not 250 ms later.
@Kostiantyn that's what I found out after much time researching the doc (at least now I know what the doc looks like :) ) @andre: maybe a foreword that the library is changing a lot and that your code isn't up to date with the latest version would help.
@shaun that's necessary if you want to rule out "triple clicks". If you don't, and want to fire immediately after the second click, @PJ 's implementation works well
I was working in my own code editor, not jsbin, and I couldn't figure out how to get it to work with the <a> element. It kept refreshing the page, and I tried event.preventDefault() but couldn't get that to work. So I just changed it to something other than an <a> element and it worked.
I think the stream need a share()
operation at the end
why the signature of throttle and buffer is different?
throttle signature :- throttle(durationSelector: function(value): Observable | Promise)
buffer signature :- buffer(closingNotifier: Observable): Observable
buffer is used to buffer until closingNotifier emits a value.
throttle is also same as buffer where it passes the new value until observable emits a value (duration selector).
why throttle needs a function which returns observable and not taking the observable directly as in case of buffer.
Thanks for the help
This is working on 6.2.1:
const { Observable, fromEvent } = rxjs
const { bufferWhen, filter, delay, debounceTime } = rxjs.operators;
const button = document.querySelector('button')
const label = document.getElementById('label')
const clickStream = fromEvent(button, 'click')
const dblClickStream = clickStream
.pipe(bufferWhen(() => clickStream.pipe(debounceTime(250))))
.pipe(filter(arr => arr.length === 2))
dblClickStream.subscribe(event => {
label.textContent = 'double click';
console.log('dbl click')
});
dblClickStream
.pipe(delay(1000))
.subscribe(suggestion => {
label.textContent = '-';
})
@Fabio Thank you so much, this really helped me out! I ended up, writing it like this
var clickStream = fromEvent(button, "click");
var dblClickStream = clickStream.pipe(
buffer(clickStream.pipe(debounceTime(250))),
filter(clicks => clicks.length === 2)
);
One might also refactor it into a separate operator, just to demonstrate how easy it is with the new syntax.
var dblClickStream = clickStream.pipe(filterConcurrentGroupsOf(2));
function filterConcurrentGroupsOf(n, eventSilenceTime = 250) {
return source =>
source.pipe(
buffer(source.pipe(debounceTime(eventSilenceTime))),
filter(list => list.length === n)
);
}
Had to use
const clicks$ = Rx.Observable.fromEvent(button, 'click')
clicks$
.bufferWhen(() => clicks$.debounceTime(250))
.map(list => list.length)
.filter(length => length >= 2)
.subscribe(totalClicks => {
console.log(`total clicks: ${totalClicks}`);
});
Too bad the course is outdated :(