Any pedants out there may take exception to the title of this post. How can we update state when state is immutable? But I think we all know what I've driving at here. Suppose we have a module that defines reducers like this...
StoreModule.forFeature('myFeature', reducers)
And our reducers are set up as follows...
export interface IMyFeatureState {
users: IUsersState;
notes: INotesState;
}
export const reducers: ActionReducerMap<IMyFeatureState> = {
users: usersReducer,
orders: ordersReducer
};
This is a situation I found myself in a while back. Everything was going swimmingly and I was happily dispatching actions that were specific to either users or orders. My folder structure kept user and order code seperate. Then along came a requirement curveball - an action that needed to update both areas of the store. At the time, I wasn't as experienced at ngrx (angular redux) and came up with a rather convoluted solution. For the purposes of my simple example, lets say we have an action that deactivates a user - and when that happens we need to deactivate all the orders for that user.
Bad solution: Dispatch the DeactivateUser action. Pick that up in an ngrx effect which calls the WebAPI and dispatches a DeactivateUserSuccess action. User reducer picks that up and deactivates the user in the store. Meanwhile there is a subscription on my component that is watching the user store and fires the DeactivateOrders action when the user gets deactivated. Bad, bad, bad. For starters, we really want the WebAPI call to deactivate the user and the orders on one hit. But that's not all. Logically, deactivating a user is a single event and we want that modelled in our system as a single redux action.
Good solution: There is a simple fact about redux effects that initially escaped my attention. Effects can return an array of actions. This isn't something that generally gets mentioned in tutorials. Effects are usually descibed as a way to call backend APIs and trigger a subsequest action. But they are also able to dispatch multiple actions in response to a single action. So my prefered solution is quite simple. Dispatch the DeactivateUser action and have an ngrx effect respond to call the WebAPI and return both the DeactivateUserSuccess and DeactivateOrdersSuccess actions. Easy as that.
@Effect()
DeactivateUser$: Observable<Action> = this.actions$.pipe(
ofType(CrossCuttingActionTypes.DeactivateUser),
mergeMap((action: DeactivateUser) =>
this.userService.deactivateUser(action.payload.userId).pipe(
switchMap((userId: int) => {
return [
new DeactivateUserSuccess({
userId: userId
}),
new DeactivateOrders({
userId: userId
})
];
}),
catchError(() => {
this.showError('Error deactivating user');
return of(
new DeactivateUserFailed({ userId: action.payload.userId }),
new DeactivateOrdersFailed({ userId: action.payload.userId })
);
})
)
)
);
TLDR: Effects can return an array of actions.
Top comments (0)