2
votes

I'm using ngrx and have a scenerio where I need to dispatch 2 actions at the same time. My state has properties for updating and updated and looks like below.

//from reducer
const defaultCardState: CardState = {
    ids: [],
    entities: {},
    loaded: false,
    loading: false,
    adding: false,
    added: false,
    updating: false,
    updated: false,
    deleting: false,
    deleted: false
};

These are the actions I'm dispatching from my component

this.store.dispatch(fromCard.updateCard({id: id1, changes: {name: name1}}))
this.store.dispatch(fromCard.updateCard({id: id2, changes: {name: name2}}))

Below are my action, reducer and effect

//Update Card Actions
export const updateCard = createAction('[Cards] Update Card', props<{id: string, changes: any}>())
export const updateCardSuccess = createAction('[Cards] Update Card Success', props<{changes: any}>());
export const updateCardFail = createAction('[Cards] Update Card Fail')

//Reducer
on(fromCards.updateCard, (state) => ({...state, updating: true, updated: false})),
    on(fromCards.updateCardSuccess, (state, action: any) => ({...cardAdapter.updateOne(action.changes, state), updated: true, updating: false})),
    on(fromCards.updateCardFail, (state, action: any) => fromCards.updateCardFail),

//Update Card Effect
updateCard$: Observable<Action> = createEffect(() => this.actions$.pipe(
    ofType(fromCardActions.updateCard),
    map((action: any) => { return {id: action.id, changes: action.changes}}),
    switchMap((action: any) => this.cardService.updateCard(action).pipe(
        map((res) => (fromCardActions.updateCardSuccess({changes: action }))),
        catchError(() => of(fromCardActions.updateCardFail))
    ))
))

What is the best way to dispatch these actions one after the other so the updating and updated fields don't conflict? If I run just one of these it works but if I dispatch them both together like shown above, only one completes. I see that both actions get dispatched but only one success action gets dispatched.

5

5 Answers

2
votes

You can dispatch multiple action in your effect and I would recommend you do that only in the effect

Consider example below

@Effect()
dispatchMultiAction$: Observable<Action> = this.actions$.pipe(
    ofType<SomeAction.Dispatch>(someActions.Dispatch),
    switchMap(_ =>
        of(
            new someActions.InitData(),
            new someActions.GetData(),
            new someActions.LoadData()
        )
    )
);
5
votes

Similar to Tony's answer, but using the correct operator:

@Effect()
dispatchMultiAction$: Observable<Action> = this.actions$.pipe(
    ofType<SomeAction.Dispatch>(someActions.Dispatch),
    mergeMap(_ => [
            new someActions.InitData(),
            new someActions.GetData(),
            new someActions.LoadData()
        ])
    )
);
2
votes

You don't, you have a different action with a payload that takes an array of cards instead of a single card and then the reducer returns the new state updated with the multiple cards. Your api should also be able to take an array so your effect can send multiple to the server.

1
votes

Technically it is really bad practice use action to dispatch another multiple actions. It is increased complicates of the application. After some time you can not debug and understand why and were actions will be dispatched. This video Mike Ryan describe Good Action Hygiene with NgRx: https://youtu.be/JmnsEvoy-gY?t=285

0
votes

Similar to xandermonkey's answer, but using the correct operator:

@Effect()
dispatchMultiAction$: Observable<Action> = this.actions$.pipe(
    ofType<SomeAction.Dispatch>(someActions.Dispatch),
    concatMap(_ => [
            new someActions.InitData(),
            new someActions.GetData(),
            new someActions.LoadData()
        ])
    )
);

Note the difference between concatMap and mergeMap. Because concatMap does not subscribe to the next observable until the previous completes, the value from the source delayed by 2000ms will be emitted first. Contrast this with mergeMap which subscribes immediately to inner observables, the observable with the lesser delay (1000ms) will emit, followed by the observable which takes 2000ms to complete.