micro
micro

Reputation: 27

How to access data from store in ngrx effect

I have an ngrx effect that is used to post an object to the API. It also needs to update a table after that call is done. (The object is part of the table response). The update call needs the current state of the pagination from the table. How can i do that?

ngrx effect:

  saveOrder = createEffect(() => {
    return this.actions.pipe(
      ofType(OrderActions.saveOrder),
      switchMap((postAction) =>
        this.orderAPIService.postSaveOrder(postAction.order).pipe(
          switchMap((returnOrder) => [
            OrderActions.saveOrderSuccess({ order: returnOrder }),
            OrderActions.getOrders({ ordersRequest: { pageSize: 25, pageNumber: 1 } }), // <-- Here i need to get the pagination state
          ]),
          tap(() => this.snackBarService.openSnackbar('complete', 'OK')),
          catchError((error) =>
            of(
              OrderActions.saveOrderFailure({
                notification: {
                  type: 'error',
                  message:
                    'there was an error',
                  time: Date.now(),
                  technicalError: error,
                },
              })
            )
          )
        )
      )
    );
  });

The getOrders also triggers an effect with an API call.

I could save the pagination state to the store with: this.paginator.page.subscribe((pageEvent) => callActionToSaveToStore(pageEvent.data); but how would i access this from the effect?

The 'stupid' solution could be to save the pagination state in a service variable and access it by injecting the service in the effects class. But that seems odd since I have a state store.

Upvotes: 1

Views: 1786

Answers (2)

hmartini
hmartini

Reputation: 138

I would try to avoid store access from an effect. If you look at this diagram, you will see that everything you need for your side effect should already be there.

You can solve this by thinking of actions as a kind of composition of EventTrigger and DTO. This means that you can give your initial action everything that is needed in the further course, e.g. the value that you fetch from the state withLatestFrom. For example, you can subscribe to the pagination in the component in which you trigger the saveOrder action and deliver the value in this action.

I don't see the call of the snackbar in the effect at all. Of course, you can solve it this way. But you could also subscribe to a value from the state in the component and then trigger the snackbar. Imagine you want to use this effect in a mobile app (e.g. Ionic) in addition to your web app. Then this won't work.

Here is my attempt to refactor the effect, taking (my) best practices into account. The values for the initial action all come from the component:

saveOrder$ = createEffect(() =>
  this.actions$.pipe(
    ofType(OrderActions.saveOrder),
    exhaustMap(({ order, pagination }) =>
      this.orderAPIService.postSaveOrder$(order).pipe(
        map(({ order }) =>
          OrderActions.saveOrderSuccess({ order, pagination })
        ),
        catchError((error) =>
          of(
            OrderActions.saveOrderFail({
              notification: {
                type: 'error',
                message: 'error',
                time: new Date(),
                technicalError: error,
              },
            })
          )
        )
      )
    )
  )
);

getOrders$ = createEffect(() =>
  this.actions$.pipe(
    ofType(OrderActions.saveOrderSuccess),
    map(({ pagination }) => ... do something with pagination ...)
  )
);

Upvotes: 0

micro
micro

Reputation: 27

The withLatestFrom was the solution. It seems odd, but also the way to do it. My effect looks like this now:

saveOrder = createEffect(() => {
    return this.actions.pipe(
      ofType(OrderActions.saveOrder),
      switchMap((postAction) =>
        this.orderAPIService.postSaveOrder(postAction.order).pipe(
          withLatestFrom(this.store.select(getOrdersTablePagination)),
          switchMap((response) => [
            OrderActions.saveOrderSuccess({ order: response[0] }),
            OrderActions.getOrders({ ordersRequest: response[1] }),
          ]),
          tap(() => this.snackBarService.openSnackbar('complete', 'OK')),
          catchError((error) =>
            of(
              OrderActions.saveOrderFailure({
                notification: {
                  type: 'error',
                  message:
                    'error',
                  time: Date.now(),
                  technicalError: error,
                },
              })
            )
          )
        )
      )
    );
  });

The Code with the switchMap Array also looks odd. Maybe there is a more beautiful way?

Upvotes: 0

Related Questions