Search code examples
ngrxngrx-storengrx-effectsngrx-reducers

Ngrx best practice in multiresponse - having multiple effects, actions and reducers or single effect, action and reducer


I am facing a problem regarding where to put some business logic, either in effects and have multiple actions which each one will map to a reducer, or having a single action and do the logic in reducers.

I will explain the situation:

We have a GraphQL request which is kinda multiresponse, it retrieves customer extra info and products based on the customer id. So from the service, I am returning something like that:

getCustomerDetail(): Observable<CustomerDetail> {
  ... graphql request
  .map(response => {
    return {
      customerDetail: response.customerDetail,
      products: response.products
    }
  })
}

This observable will emit twice, once for customerDetail and products as undefined, and once with the opposite, products and customerDetail as undefined.

This service method is called in an effect returning a success or error action, and we have several actions linked to it with filter in order to dispatch a concrete action (i.e. one for customer detail and another one for products)

getCustomerData$ = createEffect(() => {
    return this.actions$.pipe(
      ofType(CustomerActions.detailPageOpened),
      mergeMap((action) => {
        return this.customerDatasource
          .getCustomerDetail({ id: action.id })
          .pipe(
            map((res) => {
              if (!res?.error) {
                return CustomerActions.getCustomerDataSuccess({
                  data: res
                });
              }

              // ... getErrorActionByPath find an action associated to the path property in error     (customerDetail or products)
              
              const errorAction = this.getErrorActionByPath(
                res?.error,
                action.interactionId
              );
              
              return errorAction;
            })
          );
      })
    );

 getCustomerDetail$ = createEffect(() => {
    return this.actions$.pipe(
      ofType(CustomerActions.getCustomerDataSuccess),
      filter((action) => action.data.customerDetail !== undefined),
      map((action) => {
        return CustomerActions.getCustomerDetailSuccess({
          customerDetail: action.data.customerDetail
        });
      })
    );
  });

 // ... another effect like this one for products

Each action dispatched by the filtered effects will be linked to a reducer to update the state with the value from the action.

In terms of best practice, my question is: what would be the best approach for doing such a thing?

Returning the same action in each query response and map it to a different reducer (it will increase de boilerplate code as I would need 2 actions per data emitted, success and error)?

Or dispatching always the same action linked to a single reducer which responsability would be to update the concrete properties (it would turn into a multiple call to the same reducer and we may lose the tracking of the actions as it would be always the same action)?

Many thanks in advance!


Solution

  • I'm not sure if I understand the question correctly, but... It's recommended to use actions as unique events, for your case this means a success and error action for each API response. This practice is called good action hygiene, for more info see https://ngrx.io/guide/eslint-plugin/rules/good-action-hygiene

    The dispatched action can then be handled by one or multiple reducers and/or effects.