angular-architects / ngrx-toolkit

Various Extensions for the NgRx Signal Store
MIT License
98 stars 16 forks source link

access store from effects in WithRedux #35

Open Chudroy opened 2 months ago

Chudroy commented 2 months ago

as of right now i'm not seeing a way to access state of a signal store from effects in using WithRedux. Would it be possible to add a parameter to effects to make the store accessible? something like

effects(actions, create, store) { 
...
}
rainerhahnekamp commented 2 months ago

@Chudroy I'm not sure if that's technically possible. We are within a signalFeature and have, therefore, no access to the final store.

Could you describe your use case maybe so that I get a better understanding?

Chudroy commented 2 months ago

hi, sure! for example, I used to have a withMethods in my signal store, that looked like this

...
  withMethods(
    (
      store,
      campaignsService = inject(CampaignsService),
      ...
    ) => {
...

and a function within this that was like so:

const loadMachineData = rxMethod<{
        page: number;
        pageSize: number;
        order?: { field: string; order: string };
      }>(
        pipe(
          tap(() => patchState(store, { isLoading: true })),
          switchMap(({ page, pageSize, order }) => {
            return campaignsService
              .getMachineData(
                store.selectedMachine(),
                store.selectedCampaign(),
                page,
                pageSize,
                order,
              )
              .pipe(
                tapResponse(
                  (response) => {
                  ....
                  },
                  (error: HttpErrorResponse) => {
                    ...
                  },
                ),
              );
          }),
        ),
      );

Basically, what I was able to achieve here was call loadMachineDatawithout having to pass in a machine or campaign as an arugment, because i could retrieve store.selectedMachine(), and store.selectedCampaign(), directly from the store's state.

Now that i've refactored my signal store to use withRedux, in my effects, I can't access the store, so i have to select those pieces of state in the component, and pass them as arguments to the action that activates the effect:

in the component:

  loadMachineData(): void {
    const campaign = this.campaignsStore.selectedCampaign();
    const machine = this.campaignsStore.selectedMachine();
    if (!campaign || !machine) return;
    const page = this.paginator.pageIndex + 1;
    const pageSize = this.paginator.pageSize;
    const order = { field: this.sort?.active, order: this.sort?.direction };
    this.campaignsStore.loadMachineData({
      campaign,
      machine,
      page,
      pageSize,
      order,
    });
  }

and in the effect:

        loadMachineData$: create(actions.loadMachineData).pipe(
          switchMap(({ campaign, machine, page, pageSize, order }) => {
            return campaignsService
              .getMachineData(machine, campaign, page, pageSize, order)
              .pipe(
                tapResponse(
                  (response) => {
                    actions.loadMachineDataSuccess({
                      data: response?.data || [],
                      machine,
                      campaign,
                    });
                  },
                  (error: HttpErrorResponse) => {
                    httpErrorSnackbarService.showHttpErrorSnackbar(error);
                    actions.loadMachineDataFailure({ error });
                  },
                ),
              );
          }),
        ),

how i think it could be better:

in component:

  loadMachineData(): void {
    const page = this.paginator.pageIndex + 1;
    const pageSize = this.paginator.pageSize;
    const order = { field: this.sort?.active, order: this.sort?.direction };
    this.campaignsStore.loadMachineData({
      page,
      pageSize,
      order,
    });
  }

adding a store parameter in the function.

 effects(actions, create, store) {
  loadMachineData(): void {
    const page = this.paginator.pageIndex + 1;
    const pageSize = this.paginator.pageSize;
    const order = { field: this.sort?.active, order: this.sort?.direction };
    this.campaignsStore.loadMachineData({
      store.selectedMachine(),
      store.selectedCampaign(),
      page,
      pageSize,
      order,
    });
  }
...
}

On a side-note, I've researched accessing store from effects, and it seems to be a common practice, see https://stackoverflow.com/questions/49177620/bad-practice-to-use-store-in-effect

"Now NgRX (v12) provides its own operator for using the store inside an effect. You don't need the two concatMap, of, withLatestFrom solution anymore. Now you can use concatMapFrom from the @ngrx/effects library like follows: ..."

ngaritagoitia commented 1 month ago

hi,

any thoughts or news on this issue?

rainerhahnekamp commented 1 month ago

Yes, we see the requirement for it and will be made available. I'm currently stuck on #44, but maybe this one here should get higher priority.

rainerhahnekamp commented 1 month ago

A quick update on that one. In order to avoid having too many breaking changes, I'd like to introduce this new feature for the new actions.

We got some feedback from the NgRx team, that we shouldn't have the actions defined in withRedux but - as in the global Store - outside and make it therefore globally available. So other Stores could consume and use these actions in their reducers and effects.

Another side effect will be that the actions will not add methods to the signalStore anymore but need to be dispatched.

We are checking if that requires some kind of Dispatcher service or if an action could be self-dispatchable, meaning you can call it from wherever you want and it will dispatch itself. Alternatively a stand-alone function like dispatch() is also on the table.

In order to get access to the store:

@ngaritagoitia, @Chudroy, what is your opinion on that?

Chudroy commented 1 month ago

Looks good, component store can communicate with global store in a better way, and i understand this also provides access to the store from the effects, so cool. Looking forward to the update!