Search code examples
angularreduxngrxngrx-storengrx-effects

Dispatch a Action when the other action is successful


I am new to the angular and redux world. I think I have a common question, but I can't find the right answer or I just need a helpful tip. I am using ngrx in my application and I want to update some basic data of a user. Then I want to reload a user's data.

In a nutshell (1) Update user data (2) Load user data (3) visualize it in the front end

If I first dispatch to update and then load the data, it doesn't always work. I have learned that the dispatcher runs async and therefore it cannot be said which one will finish first.

My idea. I would have to (1) update the user data with a promise and then (2) load and display the user data. Or am I wrong?

Thank you for your help


Here are my Code

currenUser.effects.ts

[...]

@Injectable()
export class CurrentUserEffects {

  constructor(
    private actions$: Actions,
    private currentUserService: CurrentUserService
    ) {}

    @Effect()
    loadCurrentUser$ = this.actions$.pipe(
      ofType(ActionTypes.LoadCurrentUser),
      mergeMap((action: LoadCurrentUserAction) =>
        this.currentUserService
          .getCurrentUserNew()
          .pipe(
              map(
                (currentUser: CurrentUserNew) => new LoadCurrentUserActionFinished(currentUser)
              )
          )
      )
    );

    @Effect()
    updateCurrentUser$ = this.actions$.pipe(
      ofType(ActionTypes.UpdateCurrentUser),
      switchMap((action: UpdateCurrentUserAction) =>

        // update
        this.currentUserService
          .updateCurrentUser(action.oldCurrentuser, action.newCurrentUser)
          .then(
            response => {
                return new UpdateCurrentUserActionFinished(response);
            }
          )

        // here relaod data?

      )
    );
}

currentUser.actions.ts

import { CurrentUserNew } from './../../models/currentUserNew';
import { Action } from '@ngrx/store';

export enum ActionTypes {
  LoadCurrentUser = '[CurrentUser] Load current user',
  LoadCurrentUserFinished = '[CurrentUser] Load current user finished',

  UpdateCurrentUser = '[CurrentUser] Update current user',
  UpdateCurrentUserFinished = '[CurrentUser] Update current user finished',
}

export class LoadCurrentUserAction implements Action {
  readonly type = ActionTypes.LoadCurrentUser;
}

export class LoadCurrentUserActionFinished implements Action {
  readonly type = ActionTypes.LoadCurrentUserFinished;
  constructor(public payload: CurrentUserNew) {}
}

export class UpdateCurrentUserAction implements Action {
  readonly type = ActionTypes.UpdateCurrentUser;
  constructor(
    public oldCurrentuser: CurrentUserNew,
    public newCurrentUser: CurrentUserNew
     ) {}
}

export class UpdateCurrentUserActionFinished implements Action {
  readonly type = ActionTypes.UpdateCurrentUserFinished;
  constructor(public payload: boolean) {}
}

export type CurrentUserActions =
  | LoadCurrentUserAction
  | LoadCurrentUserActionFinished
  | UpdateCurrentUserAction
  | UpdateCurrentUserActionFinished;

currentUser.reducer.ts

import { CurrentUserNew } from './../../models/currentUserNew';
import { CurrentUserActions, ActionTypes } from './currentUser.actions';

export interface ReducerCurrentUserState{
  currentUser: CurrentUserNew;
  isUpdated: boolean;
}

export const initialState: ReducerCurrentUserState = {
  currentUser: null,
  isUpdated: false
};

export function currentUserReducer(
  state = initialState,
  action: CurrentUserActions
): ReducerCurrentUserState {


  switch (action.type) {
    case ActionTypes.LoadCurrentUser: {
      return {
        ...state,
      };
    }

    case ActionTypes.LoadCurrentUserFinished: {
      return {
        ...state,
        currentUser: action.payload,
      };
    }

    case ActionTypes.UpdateCurrentUser: {
      return {
        ...state,
      };
    }

    case ActionTypes.UpdateCurrentUserFinished: {
      return {
        ...state,
        isUpdated: action.payload,
      };
    }

    default:
      return state;
  }

}

currentUser.selectors.ts

import { ReducerCurrentUserState, currentUserReducer } from './currentUser.reducer';
import { ActionReducerMap, createFeatureSelector, createSelector } from '@ngrx/store';

export const currentUserFeatureStateName = 'currentUserFeature';

export interface CurrentUserState {
  currentUser: ReducerCurrentUserState;
}

export const currentUserReducers: ActionReducerMap<CurrentUserState> = {
  currentUser: currentUserReducer,
};

// extract the main property from the state object
export const getCurrentUserFeatureState = createFeatureSelector<CurrentUserState>(
  currentUserFeatureStateName
);

export const getCurrentUser = createSelector(
  getCurrentUserFeatureState,
  (state: CurrentUserState) => state.currentUser.currentUser
)

export const isCurrentUserUpdatedSuccessful = createSelector(
  getCurrentUserFeatureState,
  (state: CurrentUserState) => state.currentUser.isUpdated
)


Solution

  • At first I tried to call two action in one effect

        @Effect()
        updateAndLoadCurrentUser$ = this.actions$.pipe(
            ofType(ActionTypes.UpdateAndLoadCurrentUser),
            switchMap((action: UpdateCurrentUserAction) =>
                of(
                    new UpdateCurrentUserAction(action.oldCurrentuser, action.newCurrentUser),
                    new LoadCurrentUserAction(),
                    new UpdateAndLoadCurrentUserActionFinished()
                )
            )
        );
    
    

    Here is the problem that the action deserve me the answer asynchron.

    My solution:

    Actuelly my solution is that i call one action. The action call a http request to the backend and the backend provides me the correct object.

    However, I would now be interested in how I can trigger two actions after one another. Unfortunately I haven't found a solution for this yet