Draggable component implementation with rxjs - 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!

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

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

redux-observable: Returning interval from an Epic

I'm trying to convert the following #ngrx effect to a redux-observable epic:
#Effect()
startReading$ =
this.actions$.ofType('START_READING').switchMap(() => {
return Observable.interval(200)
.takeUntil(
this.actions$.ofType('ABORT_PAGE_PROCESSING))
.mapTo({ type: 'PROCESS_PAGE' });
});
My attempt was to do:
export const startReadingEpic = action$ =>
action$
.ofType('START_READING')
.switchMap(() =>
Observable.interval(200)
.takeUntil(action$.ofType('ABORT_PAGE_PROCESSING'))
.mapTo({ type: 'PROCESS_PAGE' })
);
which doesn't seem to work. Returning a single Observable.of({ type: 'PROCESS_PAGE' }) works fine though.
Did I miss anything required by redux-observable that is handled out-of-the box by #ngrx ?
Thanks.
Code looks fine. It's just normal Rx, agnostic of redux-observable or ngrx (except for the ofType operator` but it's identical in both libraries)
Here it is, working:
https://jsbin.com/nosabuy/edit?js,output
There must be a problem somewhere else in your app. Check your console for errors, but if you don't see any you might try using "Pause on Caught Exceptions" in Chrome to see if maybe someone is swallowing an error silently. There's an outstanding RxJS bug where Rx itself may swallow errors and in the case of redux-observable it might affect you if there is an error in your reducers: https://github.com/redux-observable/redux-observable/issues/263. That said, I caution you not to immediately assume you're being bitten by this bug. Your debugger is your best friend here to confirm what's going on.
This section is just to bypass stackoverflow's validation which wouldn't let me submit jsbin link without code
// ignore this

Resources