Reputation: 2028
So I've been trying to figure this out for 2 days and I gave up, rxjs is very tricky specially when it comes to the map
family.
I have an action MessageRequested
with an id
as the paramter, that should initiate an api call to get the Message, and then check whether the corresponding visitor is in the store or not. If it's not in the store it will dispatch two actions, MessageLoaded
and VisitorRequested
. If it's in the store, we will just dispatch one action, MessageLoaded
. So logic would be in that order:
MessageRequested
was dispatched with an id
as the inputid
as the parameter, we have a Message
object returned. Message definition below. It has a visitor_id
property: mergeMap((action) => this.converseService.getMessage( action[0].message_id ))
visitor_id
in the Message
object exists in the Visitor state (this.store.pipe(select(selectVisitorById( m.visitor_id ) )
)Now we have an if condition we need to apply,
a. if select
returned something, then the visitor is already in the store, and we will return just the MessageLoaded
action.
b. If select
didnt return a visitor
, we now have to dispatch an additional action VisitorRequested
to request the visitor. We still need to dispatch the MessageLoaded
action. So thats 2 actions.
Now to the code:
loadMessage$ = this.actions$
.pipe(
ofType<MessageRequested>(MessageActionTypes.MessageRequested),
mergeMap((action) => this.converseService.getMessage( action[0].message_id )),
map(m => m.model),
withLatestFrom( this.store.pipe(select(selectVisitorById( m.visitor_id ) ))),
tap( ([message,visitor]) => {
if (visitor == null) new VisitorRequested(message.visitor_id);
}),
map( ([message, visitor]) => {
return new MessageLoaded({message});
} )
);
export class Message {
id: number;
message: string;
visitor_id: number;
is_from_visitor: boolean;
created_at: string;
}
export class MessageResponse extends Response {
model: Message
}
export class Response{
message:String;
didError: boolean;
errorMessage: string;
}
Code looks alright, except that withLatestFrom
does not accept inputs, so I can't pass the m.visitor_id
variable, and now I have to think of another approach. I tried using forkJoin
and concatMap
, but didnt make it.
What would be right operators to utilize in the scenario?
This is what finally worked with the help of all the answers:
@Effect()
loadMessage$ = this.actions$
.pipe(
ofType<MessageRequested>(MessageActionTypes.MessageRequested),
mergeMap((action) => this.converseService.getMessage( action.message_id )),
map(m => m.model),
concatMap(message =>
of(message).pipe(
withLatestFrom(this.store.pipe(select(selectVisitorById(message.visitor_id))))
)
),
tap( ([message,visitor]) => {
if (!(visitor)) this.store.dispatch(new VisitorRequested(message.visitor_id));
}),
map( ([message, visitor]) => {
return new MessageLoaded({message});
} )
);
Upvotes: 1
Views: 1351
Reputation: 885
If you want to pass down some data from the store along with data from an action, you can pass a map function to withLatestFrom()
:
someEffect$ = createEffect(() =>
this.actions$.pipe(
ofType(SOME_ACTION),
withLatestFrom(this.store.select(yourFeature), (action, data) => ({ dataFromStore: data, payload: action.payload })),
switchMap(props => {
// do something fun
})
)
);
As stated in https://ngrx.io/guide/effects, it is also recommended to wrap withLatestFrom in a flattening operator, because withLatestFrom will listen through the select()
right away regardless of the action coming in.
concatMap(action => of(action).pipe(
withLatestFrom(this.store.pipe(select(yourFeature)))
))
Upvotes: 0
Reputation: 15505
tap
does not return a value, it's just a void.
Instead, return an array of actions depending on the condition:
switchMap(([message,visitor]) => {
if (visitor == null) return [new VisitorRequested(message.visitor_id), new MessageLoaded({message})];
return [new MessageLoaded({message})]
})
Imho, the more correct answer would be to have 2 effects:
1) listen to MessageActionTypes.MessageRequested
and dispatch MessageLoaded
2) listen to MessageActionTypes.MessageRequested
and dispatch VisitorRequested
if it isn't in the store. Or even, put the logic of VisitorRequested
in this effect.
Upvotes: 1