RxJS 5 - function called twice - rxjs5

I'm trying to show a confirmation Modal, before deleting a line using RxJS.
The code below works fine if I delete 1 line.
When I delete a second line, then deleteLineFulfilled is called 2 times.
If I delete a third line, then deleteLineFulfilled is called 3 times and so on...
Any idea why?
const deleteLineEpic = (action$, store) =>
action$.ofType(DELETE_LINE_REQUEST)
.flatMap((action) => Observable.merge(
Observable.of(showModalYesNo('CONFIRM_DELETE')),
action$.ofType(MODAL_YES_CLICKED).map(() =>
deleteLineFulfilled(action.line)
)
.takeUntil(action$.ofType(MODAL_NO_CLICKED))
));

action$ is a perpetual observable, and it will only stop when an action of the type MODAL_NO_CLICKED is dispatched - from your code it's hard to say when that happens, you should add an .take(1) before the .takeUntil(...).
However with this architecture you have to make sure that there has to be either an MODAL_YES_CLICKED or MODAL_NO_CLICKED emitted and that the emission cannot be skipped.
A simpler way would be to implement the confirm-dialog, subscribe to the result and then only dispatch the delete-action if the result was YES and if the result was NO don't even dispatch the action. That way you'll have a much cleaner action-epic.

Try with this:
.first() // $action is a perpetual observable. Only take the first one.
.takeUntil(action$.ofType(MODAL_NO_CLICKED))

Related

DebounceTime after first value in RxJS

I need a specific behavior that I can't get with the RxJS operators. The closest would be to use DebounceTime only for values entered after the first one, but I can't find a way to do it. I have also tried with ThrottleTime but it is not exactly what I am looking for, since it launches intermediate calls, and I only want one at the beginning that is instantaneous, and another at the end, nothing else.
ThrottleTime
throttleTime(12 ticks, { leading: true, trailing: true })
source: --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval: --[~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~]--------
output: --0-----------3-----------6-----------7-----------9--------
source_2: --0--------1------------------2--------------3---4---------
throttle interval: --[~~~~~~~~~~~I~~~~~~~~~~~]---[~~~~~~~~~~~]--[~~~~~~~~~~~I~
output_2: --0-----------1---------------2--------------3-----------4-
DebounceTime
debounceTime(500)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: -----------1--------3--------------------------------13---------
What I want
debounceTimeAfterFirst(500) (?)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: --0--------1--3------------4-------------------------13---------
As you see, the debounce time is activated when a new value is entered. If the debounce time passes and any new value has been entered, it stops the listening the debounceTime action and waits to start a new one.
Edit: I forgot to comment that this must be integrated with NgRx’s Effects, so it must be a continuous stream that mustn't be completed. Terminating it would probably cause it to stop listening for dispatched actions.
I would use a throttle combined with a debounceTime:
throttle: from Documentation Emit value on the leading edge of an interval, but suppress new values until durationSelector has completed.
debounceTime: from Documentation Discard emitted values that take less than the specified time between output.
I would use a throttle stream to get the raising edge (the first emission) and then the debounce stream would give us the falling edge.
const source = fromEvent(document.getElementsByTagName('input'), 'keyup').pipe(
pluck('target', 'value')
);
const debounced = source.pipe(
debounceTime(4000),
map((v) => `[d] ${v}`)
);
const effect = merge(
source.pipe(
throttle((val) => debounced),
map((v) => `[t] ${v}`)
),
debounced
);
effect.subscribe(console.log);
See RxJS StackBlitz with the console open to see the values changing.
I prepared the setup to adapt it to NgRx which you mention. The effect I got working is:
#Injectable({ providedIn: 'root' })
export class FooEffects {
switchLight$ = createEffect(() => {
const source = this.actions$.pipe(
ofType('[App] Switch Light'),
pluck('onOrOff'),
share()
);
const debounced = source.pipe(debounceTime(1000), share());
return merge(source.pipe(throttle((val) => debounced)), debounced).pipe(
map((onOrOff) => SetLightStatus({ onOrOff }))
);
});
constructor(private actions$: Actions) {}
}
See NgRx StackBlitz with the proposed solution working in the context of an Angular NgRx application.
share: This operator prevents the downstream paths to simultaneously fetch the data from all the way up the chain, instead they grab it from the point where you place share.
I also tried to adapt #martin's connect() approach. But I don't know how #martin would "reset" the system so that after a long time if a new source value is emitted would not debounce it just in the same manner as you first run it, #martin, feel free to fork it and tweak it to make it work, I'm curious about your approach, which is very smart. I didn't know about connect().
#avicarpio give it a go on your application and let us know how it goes :)
I think you could do it like the following, even though I can't think of any easier solution right now (I'm assuming you're using RxJS 7+ with connect() operator):
connect(shared$ => shared$.pipe(
exhaustMap(value => merge(
of(value),
shared$.pipe(debounceTime(1000)),
).pipe(
take(2),
)),
)),
Live demo: https://stackblitz.com/edit/rxjs-qwoesj?devtoolsheight=60&file=index.ts
connect() will share the source Observable and lets you reuse it in its project function multiple times. I'm using it only to use the source Observable inside another chain.
exhaustMap() will ignore all next notifications until its inner Observable completes. In this case the inner Observable will immediately reemit the current value (of(value)) and then use debounceTime(). Any subsequent emission from source is ignored by exhaustMap() because the inner Observable hasn't completed yet but is also passed to debounceTime(). Then take(2) is used to complete the chain after debounceTime() emits and the whole process can repeat when source emits because exhaustMap() won't ignore the next notification (its inner Observable has completed).
Here's a custom operator that (as far s I can tell) does what you're after.
The two key insights here are:
Use connect so that you can subscribe to the source twice, once to ignore emissions with exhaustMap and another to inspect and debounce emissions with switchMap
Create an internal token so that you know when to exit without a debounced emission. (Insures that from your example above, the 4 is still emitted).
function throttleDebounceTime<T>(interval: number): MonoTypeOperatorFunction<T> {
// Use this token's memory address as a nominal token
const resetToken = {};
return connect(s$ => s$.pipe(
exhaustMap(a => s$.pipe(
startWith(resetToken),
switchMap(b => timer(interval).pipe(mapTo(b))),
take(1),
filter<T>(c => c !== resetToken),
startWith(a)
))
));
}
example:
of(1,2,3,4).pipe(
throttleDebounceTime(500)
).subscribe(console.log);
// 1 [...0.5s wait] 4

What alternatives are there to takeUntil with redux-observable and RxJS

I've started porting some async logic to redux-observables and I'm struggling a little with this.
My use case is this:
I have a UI overlay that's supposed to open on an OPEN_OVERLAY action, and automatically close after three seconds (by emitting CLOSE_OVERLAY) UNLESS either an INTERRUPT_CLOSE_OVERLAY1 or INTERRUPT_CLOSE_OVERLAY2 action is received, in which case the overlay shouldn't close.
My first take was this:
export const epic = action$ => action$.pipe(
filter(action => action.type === "OPEN_OVERLAY"),
delay(3000),
takeUntil(action$.ofType("INTERRUPT_CLOSE_OVERLAY1", "INTERRUPT_CLOSE_OVERLAY2")),
mapTo({type: "CLOSE_OVERLAY})
);
Which works, but just once. That is, if you open the overlay and interrupt it, then after closing and opening it again it will never auto-close.
I understand that this happens because takeUntil effectively unsubscribes from the Observable. However the way I understand it with redux-observables you only define the epic once at setup and you're not supposed to be subscribing/unsubscribing.
So, how would I structure this that open/close/autoclose always works without unsubscribing?
Here would be my approach:
actions$.pipe(
filter(action => action.type === "OPEN_OVERLAY"),
switchMap(
() => timer(3000)
.pipe(
mapTo({ type: "CLOSE_OVERLAY" }),
takeUntil(action$.ofType("INTERRUPT_CLOSE_OVERLAY1", "INTERRUPT_CLOSE_OVERLAY2")),
)
)
)
It starts the timer when "OPEN_OVERLAY" is emitted, then if the observable provided to takeUntil does not emit in the meanwhile, it will successfully emit "CLOSE_OVERLAY". Otherwise, the inner observable will complete, so there is no way it can emit unless "OPEN_OVERLAY" restarts it.

How can I delay an observable only if it returns faster than the delay

Take for example:
this.http.get('/getdata').pipe(delay(2000))
I would like this request to take a minimum of 2s to complete, but not any longer than it takes for the request to complete.
In other words:
if the request takes 1s to complete, I want the observable to complete in 2s.
if the request takes 3s to complete, I want the observable to complete in 3s NOT 5s.
Is there some other pipe other than delay() that can achieve this that I don't know about or is there a way to build a custom pipe for this if necessary?
The use case is to show a loader, however if the request completes too fast it doesnt look good when the loader just "flashes" for a split second
To answer the question as asked, you could simply use combineLatest() to combine a timer(2000) observable and the request observable, then just ignore the result from the timer observable. It works because combineLatest waits until all observables have emitted at least one value before emitting one itself.
combineLatest(this.http.get('/getdata'), timer(2000), x => x)
Thanks to GregL, I updated this to just use a forkJoin. This will get the latest value of the streams. But if you want to check it on every emmission, you can replace forkJoin with combineLatest and that will work too.
In my working example:
this.ibanSubscription = forkJoin({
iban: this.ibantobicService.getById(Iban),
timer: timer(1000) //so now the ajax call will take at least 1 second
}
).pipe(
map( (stream: any) => <BicModel>stream.iban),
switchMap( (bic: BicModel) => of(this.processIbanData(bic))),
catchError((error: any) => of(this.messageList.handleError(error))),
finalize(() => this.loadIbanToBicFinalize())
).subscribe();

Any need to call unsubscribe for RxJS first()

In the following code:-
RxJS.Observable.of(1,2).first().subscribe((x) => console.log(x););
is it necessary to unsubscribe given the operator first()?
No. It unsubscribes automatically after calling first(). The current syntax is observable.pipe(first()).subscribe(func); for RxJS 6.
The documentation states:
If called with no arguments, first emits the first value of the source Observable, then completes.
For the example provided, you dont need to unsubscribe, and neither you need to call first, as Observable.of(1) actually completes after emitting its first (and last) value.
first() will complete after the first item is emitted from the observable.
Also subscribe() takes three arguments, the last one being the complete callback. Running the following code will output 1 followed by 'done'.
Rx.Observable.of(1)
.subscribe(
(x) => console.log(x), // next
(x) => console.error(x), // error
() => console.log('done') // done
)

RXJS repeat does not have a chance to repeat?

I have the following epic I use in my application to handle api requests:
action$ => {
return action$.ofType(actions.requestType)
.do(() => console.log('handled epic ' + actions.requestType))
.switchMap((action) => (
Observable.create((obs) => {
obs.next({ type: type, value: action.value, form: action.form });
})
.debounceTime(250)
.switchMap((iea) => (
Observable.ajax(ajaxPost(url(iea.value), body ? body(iea.value) : action.form))
.mergeMap(payload => {
return Observable.merge(
Observable.of(actions.success(payload)),
/* some other stuff */
);
})
.catch(payload => {
return [actions.failure(payload)];
})
))
))
.takeUntil(action$.filter((a) => (a.type === masterCancelAction))
.repeat();
};
Basically, any time I perform an api request, I dispatch a request action. If I dispatch another request quickly, the previous one is ignored using debounceTime. Additionally, the request can be cancelled using the masterCancelAction and when cancelled repeat() restarts the epic. This epic works as intended in all cases expect one.
The failure case occurs when a user uses the browser back during a request. In this case I fire the masterCancelAction to the request. However, on the same execution context as a result from the masterCancelAction, another request action dispatches to perform a new request on the same epic, but the api request does not occur (the console.log does occur though) as if there was no repeat(). In other cases where cancels occur, the next request is not invoked from the same execution context and it works fine, so it seems in this case my code does not give repeat a chance to restart the epic?
A dirty workaround I found was to use setTimeout(dispatch(action), 0) on the request that dispatches after the cancellation. This seems to allow repeat() to execute. I tried passing different schedulers into repeat, but that didn't seem to help. Also, attaching takeUntil and repeat into my inner switchMap solves the problem, but then other cases where my next request does not execute in the same call stack fail.
Is there a way I can solve this problem without using setTimeout? Maybe it is not a repeat related problem, but it seems to be the case.
Using rxjs 5.0.3 and redux-observable 0.14.1.
The issue is not 100% clear without something like a jsbin to see what you mean, but I do see some general issues that might help:
Anonymous Observable never completes
When creating a custom anonymous Observable it's important to call observer.complete() if you do indeed want it to complete. In most cases, not doing so will cause the subscription to be a memory leak and might also other strange behaviors
Observable.create((observer) => {
observer.next({ type: type, value: action.value, form: action.form });
observer.complete();
})
Observable.of would have been equivalent:
Observable.of({ type: type, value: action.value, form: action.form })
However, it's not clear why this was done as the values it emits are in captured in scope.
debounceTime in this case does not debounce, it delays
Since the anonymous observable it's applied to only ever emits a single item, debounceTime will act just as a regular .delay(250). I'm betting you intended instead to debounce actions.requestType actions, in which case you'd need to apply your debouncing outside the switchMap, after the action$.ofType(actions.requestType).
Observable.of accepts any number of arguments to emit
This is more of a "did you know?" rather than an issue, but I noticed you're merging your of and /* some other actions */ I assume would be other of observables merged in. Instead, you can just return a single of and pass the actions as arguments.
Observable.of(
actions.success(payload),
/* some other actions */
actions.someOtherOne(),
actions.etc()
);
Also, when you find yourself emitting multiple actions synchronously like this, consider whether your reducers should be listening for the same, single action instead of having two or more. Sometimes this wouldn't make sense as you want them to have completely unrelated actions, just something to keep in mind that people often forget--that all reducers receive all actions and so multiple reducers can change their state from the same action.
.takeUntil will stop the epic from listening for future actions
Placing the takeUntil on the top-level observable chain causes the epic to stop listening for action$.ofType(actions.requestType), which is why you added the .repeat() after. This might work in some cases, but it's inefficient and can cause other hard to realize bugs. Epics should be thought of instead as sort of like sidecar processes that usually "start up" with the app and then continue listening for a particular action until the app "shuts down" aka the user leaves the app. They aren't actually processes, it's just helpful to conceptually think of them this way as an abstraction.
So each time it matches its particular action it then most often will switchMap, mergeMap, concatMap, or exhaustMap into some side effect, like an ajax call. That inner observable chain is what you want to make cancellable. So you'd place your .takeUntil on it, at the appropriate place in the chain.
Summary
As mentioned, it's not clear what you intended to do and what the issue is, without a more complete example like a jsbin. But strictly based on the code provided, this is my guesstimate:
const someRequestEpic = action$ => {
return action$.ofType(actions.requestType)
.debounceTime(250)
.do(() => console.log('handled epic ' + actions.requestType))
.switchMap((action) =>
Observable.ajax(ajaxPost(url(action.value), body ? body(action.value) : action.form))
.takeUntil(action$.ofType(masterCancelAction))
.mergeMap(payload => {
return Observable.of(
actions.success(payload),
/* some other actions */
...etc
);
})
.catch(payload => Observable.of(
actions.failure(payload)
))
);
};
Check out the Cancellation page in the redux-observable docs.
If this is a bit confusing, I'd recommend digging a bit deeper into what Observables are and what an "operator" is and does so that it doesn't feel magical and where you should place an operator makes more sense.
Ben's post on Learning Observable by Building Observable is a good start.

Resources