Handle mousemove stop with RxJs - events

Is there a way to identify when user stopped moving the mouse? I can't figure it out how to tell in RxJs if user stopped for let's say 2s.
When I use it like this:
fromEvent(document, 'mousemove').pipe(
debounceTime(2000)
).subscribe(() => console.log("Stoped"));
It triggers even if I move cursor out of the screen.

Depends on a situation, but timeout or timeoutWith may work for you.
This example will emit "stop" every time when there was no mousemove events for more than 1s:
const { of, defer, concat, fromEvent } = rxjs;
const { mapTo, timeoutWith, skipUntil } = rxjs.operators;
const move$ = fromEvent(document, 'mousemove').pipe(mapTo('move'));
const moveAndStop$ = move$.pipe(
timeoutWith(
1000,
defer(() => concat(
of('stop'),
moveAndStop$.pipe(skipUntil(move$))
))
)
);
moveAndStop$
.subscribe(e => console.log(e));
<script src="https://unpkg.com/rxjs#6.3.2/bundles/rxjs.umd.min.js"></script>
<div id="app">Move your mouse over me</div>

Related

How to replace the deprecated repeatWhen(notifier) with repeat(delay)

While testing, refactoring and future-proofing a customers project, I stumbled over this little deprecation notification:
Will be removed in v9 or v10. Use repeat's delay option instead.
repeatWhen(notifier: (notifications: Observable) =>
Observable): MonoTypeOperatorFunction
Simple enough, right? But when I tried, I didn't find a simple way to do so. I have a rough idea how I could hack it. But that's not exactly what I'd like to hand over to a customer as "improved" code. So what obvious path do I fail to see, that leads from this (straight out of the rxjs documentation):
import { of, fromEvent, repeatWhen } from 'rxjs';
const source = of('Repeat message');
const documentClick$ = fromEvent(document, 'click');
***const result = source.pipe(repeatWhen(() => documentClick$));***
result.subscribe(data => console.log(data))
to this:
import { of, fromEvent, repeat } from 'rxjs';
const source = of('Repeat message');
const documentClick$ = fromEvent(document, 'click');
const result = source.pipe(repeat({ delay: ??? () => documentClick$) });
result.subscribe(data => console.log(data))
How to switch an option that accepts a number into an option that repeats whenever the event happens? Well, as said, I have an idea how to achieve it, but it would be incredibly ugly. So what am I missing?
You almost got it right, just remove the question marks :)
source.pipe(repeat({ delay: () => documentClick$ }));
This basically says "Whenever source completes, subscribe to documentClick$ and whenever this (documentClick$) emits, re-subscribe to source.
Did you want to do more with that? I didn't fully understand your last paragraph.
In my little example, where a mouse click is faked by a timer, I get an emission every 2 seconds:
import { of, repeat, timer } from 'rxjs';
const source = of('Repeat message');
const documentClick$ = timer(2000);
const result = source.pipe(repeat({ delay: () => documentClick$ }));
result.subscribe((data) => console.log(data));

rxjs: cancelling a debounced observable

I have an observable Subject that emits some changes with debouncing:
someSubject.pipe(
debounceTime(5000),
).subscribe(response => {
console.log('Value is', response);
})
Now, I need a Stop button somewhere on the screen that would cancel my debounced emit. So I create a button:
const stopObs = new Subject();
...
<button onClick={() => stopObs.next()}>Stop</button>
and modify my subscription like so:
someSubject.pipe(
debounceTime(5000),
takeUntil(stopObs),
).subscribe(response => {
console.log('Value is', response);
})
This works fine, after hitting "Stop" I stop getting values in console, but there is a problem: the observable is stopped forever. And I need it to be able to emit new values, I only need to cancel already started debounced emits.
My first thought was to create a new subject and use repeatWhen:
const startObs = new Subject();
...
<button onClick={() => startObs.next()}>Start</button>
...
someSubject.pipe(
debounceTime(5000),
takeUntil(stopObs),
repeatWhen(() => startObs)
).subscribe(response => {
console.log('Value is', response);
})
But there's another problem: if I hit "Start" button more than one time and emit more than one value to startObs, then I start getting multiple console.log's for single debounced value!
So is there a way to cancel only debounced emits without stopping the entire observable?
Since debounceTime is just
const duration = timer(dueTime, scheduler);
return debounce(() => duration);
I think you can solve the problem like this:
someSubject.pipe(
debounce(() => timer(5000).pipe(takeUntil(stopObs))),
)
If you want to send the last value when the timer is cancelled due to stopObs, you could try this:
someSubject.pipe(
debounce(
() => timer(5000)
.pipe(
takeUntil(stopObs),
isEmpty(),
)
),
)
isEmpty() will emit true immediately before a complete notification, which is what debounce needs in order to send the last received value. If the timer completes without stopObs's involvement, isEmpty will emit false instead of true, but this still works well for debounce, since it only needs a value from the inner observable.

How to code a drag-lock using RxJS?

A drag-lock interaction starts with a first double-click, followed by some mouse moves, and ends with a second double-click. It is a variant of the drag-and-drop.
I want to code this interaction in RxJS, but the big problem I face is that the same event (double-click) starts and ends the interaction (a problem we do not have while coding a drag-and-drop).
How to implement a drag-lock using RxJS?
I want to subscribe to the first-click, to all the moves, and the final double-click.
plunker: https://plnkr.co/edit/MXUtPzH9iD40VjVxLBwB?p=preview
Lets define an event - DragLockEvent which consists of type "DragLockEventType" which could be either 'initialClick', 'move' or 'finalClick', and the mouse move event, in case it is of type 'move'.
Now let's create an observable which will emit these events: dragLock$.
Inside the observable there's a flag - "dragging", indicating whether or not we are in the middle of dragging.
Now we need to listen to double click events, and for each such event we need to toggle the dragging flag, and to emit the matching event.
Then, in case we are in dragging mode, we start listening to mouse move events and report them, until we have another double click.
type DragLockEventType = 'initialClick' | 'move' | 'finalClick';
interface DragLockEvent {
type: DragLockEventType;
moveEvent?: MouseEvent;
}
const dragLock$: Observable<DragLockEvent> = Observable.create((observer: Observer<DragLockEvent>) => {
let dragging = false;
const doubleClick$ = fromEvent(document.documentElement, 'dblclick')
.pipe(takeWhile(() => !observer.closed));
const mouseMove$ = fromEvent(document.documentElement, 'mousemove')
.pipe(takeWhile(() => !observer.closed));
doubleClick$
.pipe(
tap(() => {
dragging = !dragging;
if (dragging) observer.next({ type: 'initialClick' });
else observer.next({ type: 'finalClick' });
}),
filter(() => dragging),
switchMap(() => mouseMove$.pipe(takeUntil(doubleClick$)))
)
.subscribe((e: MouseEvent) => observer.next({ type: 'move', moveEvent: e }))
});

rxjs poll for data on timer and reset timerwhen manually refreshed

I am using the following libraries in the relevant application: Angular 4.x, ngrx 4.x, rxjs 5.4.x
I have an api that I need to poll every 5 minutes. The user is also able to manually refresh the data. That data is stored in an ngrx store. I am using ngrx effects so the data is retrieved by dispatching an action of type 'FETCH'.
I want to setup a rxjs stream where it will dispatch the 'FETCH' action to the ngrx store. It will be a sliding 5 minute timer that resets when the user manually updates the store. The stream should initially emit a value when subscribed.
I'm not sure how I can reset the timer. In plain javascript I would do something like the following:
console.clear();
let timer;
let counter = 0;
function fetch() {
console.log('fetch', counter++);
poll();
}
function poll() {
if (timer != null) {
window.clearTimeout(timer);
}
timer = window.setTimeout(() => {
console.log('poll');
fetch();
}, 5000);
}
function manualGet() {
console.log('manual');
fetch();
}
fetch();
<button onClick="manualGet()">Get Data</button>
Question: How do I emit on an interval that is reset when another stream emits like the example again?
You want two components to your stream – a timer and some user input. So let's start with the user input. I'll assume some button which can be clicked:
const userInput$ = Observable.fromEvent(button, 'click');
Now we want to start a timer which resets everytime userInput$ emits. We can do that using
userInput$.switchMap(() => Observable.timer(0, 5000));
However, we also want this stream to start without the user having to first click the button. But that's also not a problem:
userInput$.startWith(null);
Now we put it all together:
Observable.fromEvent(button, 'click')
.startWith(null)
.switchMap(() => Observable.timer(0, 5000))
.subscribe(() => dispatchFetch());
Note that I am following your examples of using a 5 second timer, not a 5 minute timer (which you mentioned in the question.)
After writing it out in vanilla JS I realized that the source of the timer should be the data. I was struggling to figure out what the source would be. Clearly it couldn't be the timer since I needed to reset it.
I'm open to better options but here is how I solved it:
console.clear();
let counter = 0;
const data = new Rx.BehaviorSubject(null);
function fetch() {
data.next(counter++);
}
function manualGet() {
console.log('manual');
fetch();
}
// setup poll
data.switchMap(() => Rx.Observable.timer(5000))
.subscribe(() => {
console.log('poll');
fetch();
});
// subscribe to the data
data.filter(x => x != null).
subscribe(x => { console.log('data', x); });
// do the first fetch
fetch();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.5/Rx.min.js"></script>
<button onClick="manualGet()">Get Data</button>
With ngrx I am listening for the success action related to the fetch event.

rxjs: Subscribe each time value is emitted

I want to subscribe to mousemove event each time mouse is clicked down and unsubscribe each time mouse is clicked up.
// start when mousedown
Observable.fromEvent(element, 'mousedown')
.subscribe(() => {
Observable.fromEvent(element, 'mousemove')
// finish when mouseup
.takeUntil(Observable.fromEvent(element, 'mouseup'))
.subscribe(() => {/*do something on mousemove*/});
});
Is it possible to optimize the code (to have single "subscribe" method)?
Observable.fromEvent(element, 'mousedown')
.switchMap(() =>
Observable.fromEvent(element, 'mousemove')
.takeUntil(Observable.fromEvent(element, 'mouseup'))
)
.subscribe(() => {/*do something on mousemove*/});

Resources