rxjs: Subscribe each time value is emitted - rxjs

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*/});

Related

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 cancel async action with rxjs?

I want to trigger an async action via button Async increment but cancel it by clicking button cancel.
My code with race of rxjs does not work.
Is there any way to implement it?
https://codesandbox.io/s/zk5oy3zj7x
welcome.
switchMap cancel the previous request (completing the previous inner observable) if a new request comes before the previous is completed.
If you want to cancel request with a new stream, you could merge your stream with the "action one", and let switchMap to decide what to do.
merge(
increment$.pipe(mapTo(true)),
cancelIncrement$.pipe(mapTo(false))
).pipe(
switchMap((run) => new Promise(resolve => {
if(run)
setTimeout(() => resolve(2), 2000);
}))
).subscribe(console.log);

Handle mousemove stop with RxJs

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>

Draggable component implementation with rxjs

I moving implementation of draggable component from rxjs5 to 6. While testing i noticed that function onDragEnd is not called occasionally. What could be the reason for this ? Here is the code:
fromEvent(this.ref,"mousedown")
.pipe(
filter(this.inside),
debounceTime(300),
tap((event) => {
this.onDragStart(event);
}),
exhaustMap(() =>
fromEvent(document.body,"mousemove")
.pipe(
takeUntil(
fromEvent(document.body,"mouseup")
.pipe(tap(this.onDragEnd)) // <--- my problem
)
)
)
)
.subscribe(
(event) => {
this.onDragMove(event);
},
this.onError
)
Edit: just to clarify what i expect, mousedown event should be map to observable which (assuming i understood exhaustMap correctly) should not allow to outer observable emit again until inner observable completes. What i cant understand is why in one of 20-30 times onDragEnd is not fired, observable from document so as my intuition telling me mouse-up is inevitable in this case, am i wrong ? If you have some ideas what could possibly be wrong with this implementation please let me know, thank you!

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 }))
});

Resources