ngrx effects combine action resposne with multiple selectors values - ngrx

I have an Action defined MyAction.loadDataSuccess.
When ever loadDataSuccess is success ,we need to dispatch other three action.that am trying to implemented here. Other Actions needed loadDataSuccess response along with selectData1 and selectData2
Here is my code.
loadSuccess$ = createEffect(() => this.actions$.pipe(
ofType(MyActions.loadDataSuccess),
concatMap((action: any) => of(action).pipe(
withLatestFrom(this.store$.select(selectData1)),
withLatestFrom(this.store$.select(selectData2))
)),
switchMap(([payload, data1,data2]: any) => [
FileActions.setSelectedName({ fileName: payload.name[0] }),
PFDataActions.loadPFData({ data1: data1, data2:data2 }),
OtherDataActions.loadOtherData(
{ data1: data1, data2:data2, otherName: ''}
),
MainDataActions.loadMainData(
{ data1: data1, data2:data2, otherName: '' }
),
])
));
The above code was working when we have only one withLatestFrom.
Now the above code throwing FileActions.setSelectedName undefined.
How can we combine action response with two selector values
i have combined both selectData1 and selectData2 into a single selector,but that won't worked.
Some one have any solutions, feel free to update
Note: ngrx version-13

withLatestFrom can take multiple source observables: https://rxjs.dev/api/operators/withLatestFrom
ofType(MyActions.loadDataSuccess),
withLatestFrom(this.store$.select(selectData1), this.store$.select(selectData2)),
switchMap(([payload, [data1,data2]]: any) =>

Related

How can I dispatch one action, wait 1 second, then dispatch two more actions in Redux Observable?

How would I do the following in a single epic
Dispatch pauseGame()
Wait 1 second
Dispatch 2 actions
The following dispatches the last two actions but not pauseGame().
const moveEpic: RootEpic = (action$, state$) =>
action$.pipe(
filter(isActionOf(move)),
map(() => pauseGame()),
filter(() => state$.value.ruleRow.lastMoveSuccessful),
delay(1000),
switchMap(() => [
removeBoardObject(
state$.value.ruleRow.totalMoveHistory[state$.value.ruleRow.totalMoveHistory.length - 1]
.dragged,
),
resumeGame(),
]),
);
The reason pauseGame isn't dispatching is because you're not dispatching it. You're calling the action creator and then immediately changing your observable state to lastMoveSuccessful.
Instead, what you want is to split the pipeline and merge them back to one. I know this is confusing, but it's how Redux-Observable works at the moment. If you want a different way where you can dispatch at any point in time, checkout my article: https://dev.to/sawtaytoes/the-best-practice-anti-pattern-jj6
When the move type occurs, switch to a new observable. That observable is a merging of 2 new observables: one which immediately dispatches pauseGame, the other which checks if the last move was successful, and if so, waits a second and dispatches 2 other actions.
const moveEpic: RootEpic = (action$, state$) => (
action$.pipe(
filter(isActionOf(move)),
switchMap(() => (
merge(
of(pauseGame()),
of(state$.value.ruleRow.lastMoveSuccessful).pipe(
filter(Boolean),
delay(1000),
concatMap(() => [
removeBoardObject(
state$.value.ruleRow.totalMoveHistory[
state$.value.ruleRow.totalMoveHistory.length - 1
].dragged
),
resumeGame(),
]),
)
)
)),
);
)
As a side note, I don't know why you've created your own isActionOf function, but normally you should be able to change that line to ofType(move) instead.
Evert Bouw provided a simpler suggestion utilizing startWith and endWith. You'll lose sequential ordering in your pipeline, but you don't have to split it:
const moveEpic: RootEpic = (action$, state$) => (
action$.pipe(
filter(isActionOf(move)),
switchMap(() => (
of(state$.value.ruleRow.lastMoveSuccessful).pipe(
filter(Boolean),
delay(1000),
map(() => (
removeBoardObject(
state$.value.ruleRow.totalMoveHistory[
state$.value.ruleRow.totalMoveHistory.length - 1
].dragged
)),
startWith(pauseGame()),
endWith(resumeGame()),
)
)
)),
);
)
Keep in mind, if you needed to know the value of state$ in endWith, you'd want to use finalize instead. It takes a function instead of a value.

Dispatch action in effect before return statement

I need to dispatch/trigger another ui-related action inside an effect before calling the service to fetch data without injecting the store
I managed to fix it by injecting the store in the constructor and dispatch this extra action in effect right before calling the service for fetching the data(this.store.dispatch(UIActions.startLoading()) but I am not sure if injecting the store in the effect is a good practice
recipes$ = createEffect(() => this.actions$.pipe(
ofType(RecipeActions.FETCH_RECIPES),
switchMap(() => this.recipeService.getRecipes().pipe(
switchMap(recipes => [
RecipeActions.setRecipes({ recipes }),
UIActions.stopLoading()
]),
catchError(() => EMPTY)
))
));
I wonder if there is a way to do this like using
tap(() => of(UIActions.startLoading())) inside the first switchMap
You're correct in the use of tap operator for your desired functionality.
However, you need to do store.dispatch instead of simply returning an of observable.
Also, instead of multiple switchMap, you can use the from observable to return array of actions.
recipes$ = createEffect(() => this.actions$.pipe(
ofType(RecipeActions.FETCH_RECIPES),
tap(()=> this.store.dispatch(UIActions.startLoading())),
switchMap(() => this.recipeService.getRecipes().pipe(
map(recipes => from([
RecipeActions.setRecipes({ recipes }),
UIActions.stopLoading()
]),
catchError(() => EMPTY)
))
));

Dispatch multiple actions from in redux-observable

I am trying to dispatch multiple actions to redux. Here is my code
action$.pipe(
ofType(trigger),
mergeMap(({ payload }) =>
from(endpoint(payload)).pipe(
map(response =>
// this works fine
// setData(response.data)
// this doesn't
concat(
of(setData(response.data)),
of({ type: 'hello' })
)
// I also tried
[
of(setData(response.data)),
of({ type: 'hello' })
]
)
)
),
catchError(err => Promise.resolve(creators.setError(err)))
)
Single dispatch works, but if I try multiple items as above I am getting Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
map just maps one item to another so when you return [action1, action2] you're still returning an array and redux-observable tries to treat it as an action itself. What you want instead is "unwrapping" the array (or Observable created with concat) returned.
So instead of using map you can use mergeMap (or concatMap) and when you return an array it'll iterate it and make separate emissions for each item:
mergeMap(response => [
setData(response.data),
{ type: 'hello' },
]),
If this looks too odd you can wrap the array with from to make it more obvious:
mergeMap(response => from([
setData(response.data),
{ type: 'hello' },
])),
You can even use a single of:
mergeMap(response => of(
setData(response.data),
{ type: 'hello' },
)),

When using redux-observable, from a single action and single stream, how do I dispatch different actions based on filter

Update
Here is a working example using redux-observable. https://redux-observable-playground-ykzsyp.stackblitz.io This achieves what I want using mergeMap and if/else statement, but I was hoping to use Observable.filter as that seems more elegant.
Original question
I have an epic that currently dispatches a single action but would like to dispatch different actions based on a filter using a single stream. Here is the current code:
const streamEpic = (action$) => action$.pipe(
ofType('START_PLAYBACK_STREAM'),
switchMap(playbackStream$),
filter((playEvent) => playEvent.type === 'LOAD')),
map((playEvent) => loadActionCreator(playEvent.fileName))
// how can I filter on other types?
// and dispatch other actions?
);
I've seen many rxjs examples that use a single stream and filter to map different actions, for example:
playbackStream$
.filter((playEvent) => playEvent.type === 'LOAD'))
.map((playEvent) => loadActionCreator(playEvent.fileName));
playbackStream$
.filter((playEvent) => playEvent.type === 'START'))
.map((playEvent) => startActionCreator());
playbackStream$
.filter((playEvent) => playEvent.type === 'STOP'))
.map((playEvent) => stopActionCreator());
I'm trying to do this same thing in redux-observable but no luck. If I use tap, ignoreElements, and store.dispatch I can get the following to work but I know its an anti-pattern.
const streamLoadEvents = (action$, store) => action$.pipe(
ofType('START_PLAYBACK_STREAM'),
tap(() => {
playbackStream$
.filter((playEvent) => playEvent.type === 'LOAD'))
.map((playEvent) => store.dispatch(loadActionCreator(playEvent.fileName)));
playbackStream$
.filter((playEvent) => playEvent.type === 'START'))
.map((playEvent) => store.dispatch(startActionCreator()));
playbackStream$
.filter((playEvent) => playEvent.type === 'STOP'))
.map((playEvent) => store.dispatch(stopActionCreator()));
}),
ignoreElements()
);
I know that I could also use a switch or if/else statement inside of something like map or switchMap, like the answer here: Redux-Observable multiple actions in single epic, but I'd like to avoid this as its rather inelegant and does not take full advantage of streaming operators. The answer here: https://stackoverflow.com/a/40895613/367766 seems to get me a little closer...
What's the suggested approach here? Are the operators or example you can point me to? Thanks!
You're right, if/else should only be used as a last resort and there are probably several ways you could approach this, in any case, here's my take on this:
There's a merge method which can be viewed as a reactive equivalent of the if/else operator: you provide it with observables, and if any of them emits, the resulting observable will emit it's own value(AC) as well. When a new action comes through, we inspect it, and pick a new action creator(AC) accordingly. Let's take look at some code:
const filteredAction$ = action$.pipe(
ofType('START_PLAYBACK_STREAM'),
);
Nothing interesting here, just filtering out the action types we're not interested in, and then
const operation$ = merge(
filteredAction$.pipe(
filter((action) => action.payload.type === 'LOAD'),
mapTo(loadActionCreator),
),
filteredAction$.pipe(
filter((action) => action.payload.type === 'START'),
mapTo(startActionCreator),
),
filteredAction$.pipe(
filter((action) => action.payload.type === 'STOP'),
mapTo(stopActionCreator),
),
).pipe(
startWith(startActionCreator)
);
Here we are choosing which action creator to use based on the action's payload (please note that I'm following the flux standard for actions - the action's data is under the payload property now, there's an awesome library you could use as a helper for it. Also, you should probably rename the 'type' prop to avoid confusion).
mapTo basically tells the operation$ stream what to emit, you can think of this expression as assigning a function to some variable for later use. Every time an epic gets invoked, we'll choose a proper action creator for it here.
startWith is actually not needed here (assuming the first action always includes
the 'START' type) and is for semantic purposes only.
Last but not least, an epic has to return at least one action for dispatching:
return combineLatest(
operation$,
filteredAction$
)
.pipe(
map((arg) => {
const operation = arg[0];
const actionObject = arg[1];
return operation(actionObject);
})
)
Latest values from both streams are combined together to form a follow up action: operation$ (which yields us a proper action creator function) and filteredAction$ (always contains the action itself, we might need some data from it).

Is it possible to both dispatch an array of actions and also navigate from an ngrx effect?

I have an issue with one of my application's ngrx effects. I am basically trying to execute multiple actions using concatMap() AND navigate using the router store's go().
Here is the effect:
#Effect()
loadPersonalInfoAndSignin$: Observable<Action> = this.actions$
.ofType(session.ActionTypes.LOAD_PERSONAL_INFO)
.map((action: LoadPersonalInfoAction) => action.payload)
.do(sessionToken => {
localStorage.setItem('authenticated', 'true');
localStorage.setItem('sessionToken', sessionToken);
})
.switchMap(() => this.userAccountService
.retrieveCurrentUserAccount()
.concatMap(currentUserAccount => [
new LoadUserAccountAction(currentUserAccount),
new SigninAction(),
new LoadMessagesAction({})
])
)
.mapTo(go(['/dashboard']));
If I remove the .mapTo(go(['/dashboard'])), then all three actions in the concatMap array are successfully dispatched to their corresponding effects.
I am therefore wondering why my mapTo(go(... is causing the last two actions in the array (i.e. SigninAction & LoadMessagesAction) not to be dispatched to their corresponding effects..
Can someone please help?
edit: Changing mapTo to do as follows:
.do(go(['/dashboard']));
results in the following error:
ERROR in /Users/julien/Documents/projects/bignibou/bignibou-client/src/app/core/store/session/session.effects.ts (55,9): Argument of type 'Action' is not assignable to parameter of type 'PartialObserver<SigninAction>'.
Type 'Action' is not assignable to type 'CompletionObserver<SigninAction>'.
Property 'complete' is missing in type 'Action'.
Using do for the go call will not see the route changed. go is an action creator and the action that it creates needs to be emitted from the effect for #ngrx/router-store to receive the action and effect the route change.
Also, the mapTo operator will ignore what it receives and will emit the value you've specified, so it's not appropriate, either.
Instead, you should include the action created by the go call in your concatMap array:
#Effect()
loadPersonalInfoAndSignin$: Observable<Action> = this.actions$
.ofType(session.ActionTypes.LOAD_PERSONAL_INFO)
.map((action: LoadPersonalInfoAction) => action.payload)
.do(sessionToken => {
localStorage.setItem('authenticated', 'true');
localStorage.setItem('sessionToken', sessionToken);
})
.switchMap(() => this.userAccountService
.retrieveCurrentUserAccount()
.concatMap(currentUserAccount => [
new LoadUserAccountAction(currentUserAccount),
new SigninAction(),
new LoadMessagesAction({}),
go(['/dashboard'])
])
);

Resources