Is it possible to `skip` execution while other epic is running? - rxjs

I'm a beginner in RxJS so sorry if this doesn't make sense.
The current flow in time looks like this:
REMOVE_USER -----------------------> SUCCESS
---------------GET_DEVICE--->SUCCESS--------
The high-level goal is to skip fetching device when user is being removed.
Oversimplified epics:
const getDeviceEpic = action$ => action$.pipe(
ofType('GET_DEVICE_REQUEST'),
mergeMap(() => from(service...).pipe(
mapTo({ type: 'GET_DEVICE_SUCCESS' }))
))
const removeUser = action$ => action$.pipe(
ofType('REMOVE_USER_REQUEST'),
mergeMap(() => from(service...).pipe(
mapTo({ type: 'REMOVE_USER_SUCCESS' }))
)
)
How would I approach that?
I'm not sure if I can somehow add e.g. takeUntil(removeUserAPICall$) to device app. Or perhaps check if REMOVE_USER_REQUEST has been fired and then wait for REMOVE_USER_SUCCESS to continue.

Potentially, you could achieve that with windowToggle:
In your case "on" is REMOVE_USER_SUCCESS and "off" is REMOVE_USER_REQUEST.
So we'll be listening to GET_DEVICE_REQUEST between REMOVE_USER_SUCCESS and REMOVE_USER_REQUEST.
Note that we'll have to start with our filter open, by prepending startWith(void 0) to the "on" stream.
E.g.:
const getDeviceEpic = action$ => action$.pipe(
ofType('GET_DEVICE_REQUEST'),
windowToggle(
action$.pipe(ofType('REMOVE_USER_SUCCESS'), startWith(void 0)),
()=>action$.pipe(ofType('REMOVE_USER_REQUEST')
),
mergeMap(() => from(service...).pipe(
mapTo({ type: 'GET_DEVICE_SUCCESS' }))
))
const removeUser = action$ => action$.pipe(
ofType('REMOVE_USER_REQUEST'),
mergeMap(() => from(service...).pipe(
mapTo({ type: 'REMOVE_USER_SUCCESS' }))
)
)
* warning: written in a notepad
Yet, imho, it's also fine to have a flag on the store (maybe, as an indication of the state).
More about pausing and muting streams in my article "Pausable Observables in RxJS".
--
Hope this helps

Related

ngrx effect - catchError: returning of does not end stream, and the API call does not re-run

I have an effect in NgRX effect, as follows:
$createOrganisation = createEffect(() =>
this.actions$.pipe(
ofType(fromOrganisationActions.createOrganisation),
switchMap((data) => this.organisation.createOrganisation(data)),
map((response) => fromOrganisationActions.createOrganisationSuccess({ orgId: response.id })),
catchError((error) => {
return of(fromOrganisationActions.createOrganisationError(error));
})
)
);
However, my stream never seems to end when the catchError is triggered, i.e. in the instance of this.organisation.createOrganisation returning a 400 error.
The action, fromOrganisationActions.createOrganisationError(error) is triggered and my reducer is triggered from this... but if I re-trigger the fromOrganisationActions.createOrganisation effect, this effect runs but the API call is never made a second time.
If I configure it as follows, and dispatch it manually, it works:
$createOrganisation = createEffect(() =>
this.actions$.pipe(
ofType(fromOrganisationActions.createOrganisation),
switchMap((data) => this.organisation.createOrganisation(data)),
map((response) => fromOrganisationActions.createOrganisationSuccess({ orgId: response.id })),
catchError((error) => {
this.store.dispatch(fromOrganisationActions.createOrganisationError(error));
return throwError(error);
})
)
);
But other examples online suggest the first way should work, but it is not for me, and I do not understand why my stream never ends.
Can somebody please advise and tell me why my stream never ends in the first instance?
The catchError should be added to the inner observable.
$createOrganisation = createEffect(() =>
this.actions$.pipe(
ofType(fromOrganisationActions.createOrganisation),
switchMap((data) => this.organisation.createOrganisation(data).pipe (
map((response) =>
fromOrganisationActions.createOrganisationSuccess({ orgId: response.id })),
catchError((error) => {
return of(fromOrganisationActions.createOrganisationError(error));
})
)),
)
);
These mistakes can be caught with eslint-plugin-ngrx, more details about the rule here.

Emit before and after every retry

I have an epic, that listens for a certain action.
Once it gets the action, it should do a ajax.post
Branch
If status code is good, then emit YES
If status bad, then emit pre, wait 1s, emit post
I am struggling mightily with the last bullet, here is my code in a playground - https://rxviz.com/v/WJxGMl4O
Here is my pipeline part:
action$.pipe(
flatMap(action =>
defer(() => ajax.post('foo foo foo')).pipe(
tap(res => console.log('succeeded:', res)),
mapTo('YES'),
retryWhen(error$ =>
error$.pipe(
tap(error => console.log('got error:', error)),
merge(of('pre')), // this isnt emiting
delay(1000),
merge(of('post')) // this isnt emitting
)
)
)
)
)
I think you can achieve what you want by using catchError instead of retryWhen because retryWhen only reacts to next notifications but won't propagate them further. With catchError you get also the source Observable which you can return and thus re-subscribe. concat subscribes to all its source one after another only after the previous one completed so it'll first send the two messages pre and post and after that retry.
action$.pipe(
filter(action => action === 'hi'),
mergeMap(action =>
defer(() => resolveAfter(3)).pipe(
tap(res => console.log('succeeded:', res)),
mapTo('YES'),
catchError((error, source$) => {
console.log('retrying, got error:', error);
return staticConcat(
of('pre'),
of('post').pipe(delay(1000)),
source$,
);
}),
)
),
//take(4)
)
Your updated demo: https://rxviz.com/v/A8D7BzyJ
Here is my approach:
First, I created 2 custom operators, one that will handle 'pre' & 'post'(skipValidation) and one that will handle the logic(useValidation).
const skipValidation = src => of(src).pipe(
concatMap(
v => of('post').pipe(
startWith('pre'),
delay(1000),
),
),
);
What's important to notice in the snippet below is action$.next({ skip: true }). With that, we are emitting new values that will go through the iif operator so that we can emit 'pre' & 'post';
const useValidation = src => of(src).pipe(
filter(action => action === 'hi'),
mergeMap(action =>
defer(() => resolveAfter(3)).pipe(
tap(res => console.log('succeeded:', res)),
mapTo('YES'),
delay(1000),
retryWhen(error$ =>
error$.pipe(
tap(error => { console.log('retrying, got error:', error); action$.next({ skip: true })}),
delay(1000),
)
)
)
)
);
action$.pipe(
tap(v => console.log('v', v)), // Every emitted value will go through the `iif ` operator
mergeMap(v => iif(() => typeof v === 'object' && v.skip, skipValidation(v), useValidation(v))),
)
Here is your updated demo.

Add event listeners, and use `scan` to keep a reduced state, then remove event listeners

I am working on this sandbox here - https://stackblitz.com/edit/rxjs-g7msgv?file=index.ts
What I am trying to do is:
1) Wait for onLogin event
2) While logged in, I want to connectSocket(), and whenever the socket gets disconnected, and the app is in the foreground, I want to re-connectSocket(). (in the sandbox I have stubbed out connectSocket() to a promise that just waits 5 sec)
3) I want to repeat step 2, until onLogout event comes in
I wrote this code here, please see the sandbox and start things off by pressing the "onLogin" button.
fromEvent(document, 'onLogin')
.pipe(
switchMap(() =>
of({ isDisconnected: true, isInForeground: true }).pipe(
mergeMap(data =>
concat(
fromEvent(document, 'onDisconnect').pipe(
mergeMap(() =>
data.isDisconnected = true
)
),
fromEvent(document, 'onAppStateChange').pipe(
mergeMap(({ detail:{ state } }) =>
data.isInForeground = state === 'foreground'
)
),
).pipe(
mergeMap(({ isDisconnected, isInForeground }) => {
if (isDisconnected && isInForeground) {
return flatMap(() => connectSocket());
} else {
return EMPTY;
}
})
)
),
takeUntil(fromEvent(document, 'onLogout'))
)
)
)
.subscribe(console.log);
I use switchMap because while its running, I don't want any other login events to restart another flow.
I'm not able to get this working. I am new to rxjs.
Use startWith to Init the value and combineLatest will fire when either one of the event is triggered.
fromEvent(document, 'onLogin').pipe(
switchMap(() =>
combineLatest(
fromEvent(document, 'onDisconnect').pipe(
mapTo(true),
startWith(true)
),
fromEvent(document, 'onAppStateChange').pipe(
map(e => e.detail === 'foreground'),
startWith(true),
)
).pipe(
mergeMap(([isDisconnected, isInForeground]) =>
isDisconnected && isInForeground ? connectSocket() : EMPTY
),
takeUntil(fromEvent(document, 'onLogout'))
)
)
)

RxJs 6 - Make 2 http calls and wait for all responses

#Effect()
initDomain$: Observable<Action> = this.actions$.pipe(
ofType('INIT_DOMAIN'),
mergeMap((action: any) =>
this.http.get('https://demo.api/url1.php').pipe(
switchMap((data) => [
{type: 'INIT_IT', payload: data}
]),
catchError(() => of({type: 'INIT_IT_FAILED'}))
)
)
);
I have this angular effect (ngrx) that makes 1 request before continue. How can I make 2 requests and wait for both responses before continue? I know that forkJoin() is the answer but I'm little confused about the syntax
forkJoin(
this.http.get('myUrl'),
this.http.get('myOtherUrl')
)
OR if you have a bundle of observables in an array you could also write
const myArrayOfObservables = [
this.http.get('myUrl'),
this.http.get('myOtherUrl')
];
forkJoin(
myArrayOfObservables
)
This is because "forkJoin" uses the "spread" (...args) operator for its params.

redux-observable epic that doesn't send any new actions

Might be that I'm a noob and not fully understanding how this stuff should work yet, but I have an epic in redux-observable in which I want to use as a way to create a promise which will dispatch an action and wait for a different action before resolving. I've got it working by mapping the action to '__IGNORE__' but I really don't want to do that. Is there any way to just have an epic handle an action, but not pass anything else on?
Here's my code:
export const waitFor = (type, action) => new Promise((resolve, reject) => {
const waitForResult = action$ => action$.ofType(type).do(() => resolve()).mapTo({type: "___IGNORE___"});
registerEpic(waitForResult);
action();
});
You can throw away any next'd values from an observable chain by using the .ignoreElements() RxJS operator
action$.ofType(type)
.do(() => resolve())
.ignoreElements();
Another way of doing this (no more right or wrong) is to create an anonymous Observable that just subscribes.
const waitForResultEpic = action$ => new Observable(observer =>
action$.ofType(type)
.subscribe(() => resolve())
);
This is implicitly returning the subscription we create, so that it's attached to the lifecycle of our rootEpic as well. Because we never call observer.next(), this epic never emits any values; just like ignoreElements().
Although you didn't ask, you may eventually notice that your epic will run forever, listening for that incoming action matching the type variable. This may not be not what you want, if you want to match once then complete.
You can accomplish that using .take(1) operator.
const waitForResult = action$ =>
action$.ofType(type)
.take(1)
.do(() => resolve())
.ignoreElements();
Or
const waitForResult = action$ => new Observable(observer =>
action$.ofType(type)
.take(1)
.subscribe({
next: () => resolve(),
error: err => observer.error(err),
complete: () => observer.complete()
})
);
This will only match once per the life of the application--once received it will never do it again.

Resources