Home > OS >  Accessing state or store in effects?
Accessing state or store in effects?

Time:03-09

I was trying to access the current state/store in NgrX effect, it is giving back only initial state . but when i try to access it from component , it is working fine.

Effects class

     @Injectable()
export class RootEffects {
  /*
  To handle the behaviour of the Effect when different Action instances 
  occurs on the same effect you can change mergeMap to other operators
  */
  constructor(
    private actions$: Actions,
    private mockApi: MockApiService,
    private store: Store<any>
  ) {}



  // effect from simulating an API call success
  getMockDataEffect$ = createEffect(() =>
    this.actions$.pipe(
      ofType(ApiGetMockData),
      tap(() => {
        console.log('');
      }),
      mergeMap((action) => {
        console.log('');
        return this.mockApi.getData().pipe(
          map((res) => ApiSuccess({ data: res })),
          // catchError((error) => of(ApiError({ error }))),
          tap(() => {
            console.log('');
          })
        );
      })
    )
  );




  TestEffect$ = createEffect(() =>
    this.actions$.pipe(
      ofType(TestAction),
      tap(() => {
        console.log('test action in que');
      }),
      // this.store.select(selectAccounts)
      withLatestFrom(this.store),
      // withLatestFrom(this.store.select(getStateSelectedData)),
      mergeMap(([action, store]) => {
        console.log('action', action);
        console.log('store', store);
        console.log('test effect running');
        return this.mockApi
          .postData({
            data: 'this data suppossed to get from store from get sucssess action',
          })
          .pipe(
            map((res) => TestActionSuccess({ data: res })),
            // catchError((error) => of(ApiError({ error }))),
            tap(() => {
              console.log('test effect Finished');
            })
          );
      })
    )
  );
}

dispatching the actions:

 getApiData() {
    this.store.dispatch(fromRoot.ApiGetMockData());
    this.store.dispatch(fromRoot.TestAction());
  }

First dispatched action (fromRoot.ApiGetMockData()) triggers getMockDataEffect$ which internally calls an API and on success it dispatches ApiSuccess action.

Second Dispatched action(fromRoot.TestAction()) needs the data from ApiSuccess. but since it is async call without waiting for the response , the second action is getting dispatched.

so is there anyway we can wait for the success response from first action and then dispatch the second action.

my thoughts:

  1. initially i thought of dispatching second action (fromRoot.TestAction()) in the effects ,right after success response from the api but still did not work
  2. adding a tap operator after the mergemap and dispatch the action , still did not work
  3. i noticed dispatching an action from the store in effects is not a good practice https://github.com/timdeschryver/eslint-plugin-ngrx/blob/main/docs/rules/no-dispatch-in-effects.md

here is the stackBlitz code

CodePudding user response:

If the second action always depends on the first one's result, I think it's better to add a new @Effect to handle the ApiSuccess action, and then map it to return the other TestAction passing to it the data that you need in the @Effect of the TestAction action. (This requires changing the payload of the TestAction action to be passed from the new @Effect, and there is no need to dispatch the TestAction from the component).

You can try something like the following:

// Action
const TEST_ACTION = '[Random] test action';
export const TestAction = createAction(TEST_ACTION, props<{ data: any }>());

// New Effect
apiSuccessEffect$ = createEffect(() =>
  this.actions$.pipe(
    ofType(ApiSuccess),
    map((data) => TestAction(data))
  )
);

// TestAction Effect:
testEffect$ = createEffect(() =>
  this.actions$.pipe(
    ofType(TestAction),
    mergeMap((action) => {
      return this.mockApi.postData({ data: action.data }).pipe(
        map((res) => TestActionSuccess({ data: res })),
        tap(() => {
          console.log('test effect Finished');
        })
      );
    })
  )
);

And here is a working version of your StackBlitz


The alternative solution (not recommended) is to return both actions from the getMockDataEffect, instead of creating a new @Effect, like the following:

getMockDataEffect$ = createEffect(() =>
  this.actions$.pipe(
    ofType(ApiGetMockData),
    tap(() => {
      console.log('');
    }),
    mergeMap((action) => {
      console.log('');
      return this.mockApi.getData().pipe(
        switchMap((res) => [
          ApiSuccess({ data: res }),
          TestAction({ data: res }),
        ]),
        tap(() => {
          console.log('');
        })
      );
    })
  )
);

CodePudding user response:

The issue you have it that dispatching both right after another will just cause them to run asap, the former not waiting for the latter. This is actually a good and wanted behavior since we do not want to block the execution - it is async. If we want to act on a result of an effect we can do so by dispatching an action after the effect is done with the async call.

so is there anyway we can wait for the success response from first action and then dispatch the second action.

It is probably doable, but it is gonna be hacky and you should not do it.

Just hook up the TestAction call on the returning action of the getMockDataEffect

TestEffect$ = createEffect(() =>
    this.actions$.pipe(
      ofType(ApiSuccess),
      mergeMap(({data}) => {
        return this.mockApi
          .postData({ data })
          .pipe(
            map((res) => TestActionSuccess({ data: res })),
            // catchError((error) => of(ApiError({ error }))),
          );
      })
    )
  );
}

avoid-dispatching-multiple-actions-sequentially

  • Related