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