Search code examples
angularngrxngrx-effects

NgRx http.post request doesn't execute after was call second time by Effect


I have problem when i dispatch the login action for second or more time. With first request evrything is ok if login is successful token is acquired and user get re-routed to main page. If is unsucesfull u get message that ur login was wrong and should try again but if try to log for the second time none request is send.

The method which is call after button "Login" was clicked is method onLogin in class "AccountLoginComponent"

Reducer

import {createEntityAdapter, EntityAdapter, EntityState} from '@ngrx/entity';
import {ActionListModel} from '../models/actionListModel';
import {ActionDetailModel} from '../models/actionDetailModel';
import {createReducer, on} from '@ngrx/store';
import * as ActionActions from './action.actions';

export interface ActionState extends EntityState<ActionListModel> {
  actionDetail: ActionDetailModel,
  messageActionCreation: string,
  actionDetailStatus: string,
  actionListStatus: string,
  actionCreationStatus: string,
}

export function selectActionId(action: ActionListModel): number {
  //In this case this would be optional since primary key is id
  return action.idAction;
}

// we will add entity adapter to our init state
export const adapter: EntityAdapter<ActionListModel> = createEntityAdapter<ActionListModel>({
  selectId: selectActionId
});

// our init state is init state of EntityState
export const initialState: ActionState = {
  ...adapter.getInitialState(),
  actionDetail: null,
  messageActionCreation: "",
  actionListStatus: "",
  actionDetailStatus: "",
  actionCreationStatus: "",
};

const _actionReducer = createReducer(
  initialState,
  on(ActionActions.loadActionsPageSuccess, (state, {actionInList}) => {
    return adapter.addAll(actionInList, state);
  }),
  on(ActionActions.loadActionsPageSuccess, (state, {status}) => ({
    ...state, actionListStatus: status
  })),
  on(ActionActions.loadActionDetailSuccess, (state, {actionDetail}) => ({
    ...state, actionDetail: actionDetail, actionDetailStatus: status
  })),
  on(ActionActions.createAirsoftActionSuccess, (state, {action, status}) => ({
    ...state, actionDetail: action, actionCreationStatus: status,
  }))
);

export function actionReducer(state, action) {
  return _actionReducer(state, action);
}

// get the selectors
const {
  selectIds,
  selectEntities,
  selectAll,
  selectTotal,
} = adapter.getSelectors();

export const selectActionIds = selectIds;

export const selectActionsEntities = selectEntities;

export const selectAllActions = selectAll;

export const selectActionsTotal = selectTotal;

Actions

import {createAction, props} from '@ngrx/store';
import {AccountModel} from '../models/account.model';

export enum AccountActionsTypes {
  AccountLogin = '[Login Page] Account Login',
  AccountLoginSuccess = '[Login Page] Account Login Success',
  AccountLoginFailed = '[Login Page] Account Login Failed',

  AccountLogout = '[Header] Account Logout',

  AccountActivation = '[Activation page] Account Activation',
  AccountActivationSuccess = '[Activation page] Account Activation Success',
  AccountActivationFailed = '[Activation page] Account Activation Failed',
}

export const login = createAction(AccountActionsTypes.AccountLogin, props<{ email: string; password: string }>());

export const loginSuccess = createAction(AccountActionsTypes.AccountLoginSuccess, props< { account : AccountModel, token : string, message: string, status: string}>());

export const loginFailed = createAction(AccountActionsTypes.AccountLoginFailed);

export const logout = createAction(AccountActionsTypes.AccountLogout);

export const accountActivation = createAction(AccountActionsTypes.AccountActivation, props<{formula : string}>());

export const accountActivationSuccess = createAction(AccountActionsTypes.AccountActivationSuccess, props<{message : string}>());

export const accountActivationFailed = createAction(AccountActionsTypes.AccountActivationFailed, props<{error : string}>());

Effect

login$ = createEffect(() =>
this.actions$.pipe(
  tap( kek => {/*console.log(kek)*/}),
  ofType(AccountActionsTypes.AccountLogin),
  exhaustMap(({email: email, password: password}) => this.accountService.login(email,password)
    .pipe(
      map((response : { body: {account : AccountModel, token : string, message: string, status: string}}) =>
        ({type: AccountActionsTypes.AccountLoginSuccess, account: response.body.account, token: response.body.token, message: response.body.message, status: response.body.status })),
      catchError(error => of({type: AccountActionsTypes.AccountLoginFailed, error: error })),
      tap(response => {
        /*console.log(response)*/;
      }),
    ))
));

Service

private _options = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) };


login(email: string, password: string): Observable<Object> {
        return this.http.post('http://localhost:8080/user/login', {email: email, password: password}, this._options); }

AccountLoginComponent

onLogin() {
    this.store.dispatch(login({email : this.loginForm.value.email, password: sha1(this.loginForm.value.password)}));
    this.loginResult$ = this.store.pipe(select(selectLoginStatus));
    this.loginResultSub = this.loginResult$.subscribe(({loginStatus, message}) => {

      this.isSubscribe = true;
      this.message = message;
      if(loginStatus === 'success') {
        this.router.navigate(['/home']);
      }
    });
  }

Solution

  • The problem was in the effect because if the login was wrong i set token and account in my state to undefinied. I fix it like this:

    Repaired Effect

    login$ = createEffect(() =>
    this.actions$.pipe(
      ofType(AccountActionsTypes.AccountLogin),
      exhaustMap(({email: email, password: password}) => this.accountService.login(email,password)
        .pipe(
          map((response : { body: {account : AccountModel, token : string, message: string, status: string}}) => {
            if(response.body.token !== undefined) {
              return {type: AccountActionsTypes.AccountLoginSuccess, account: response.body.account, token: response.body.token, message: response.body.message, status: response.body.status };
            } else {
              return {type: AccountActionsTypes.AccountLoginWrong, message: response.body.message, status: response.body.status };
            }
          }),
          catchError(error => of({type: AccountActionsTypes.AccountLoginFailed, error: error })),
        )
      )
    ));
    

    New Action

    export const loginWrong = createAction(AccountActionsTypes.AccountLoginWrong, props< {message: string, status: string}>());
    

    Reducer

    const _accountReducer = createReducer(
      initialState,
      on(AccountActions.loginSuccess, (state, { account, token, message, status } ) => ({
        ...state, account: account, token: token, message: message, loginStatus: status
      })),
      on(AccountActions.loginWrong, (state, { message, status } ) => ({
        ...state, message: message, loginStatus: status
      })),
      on(AccountActions.logout, (state) => ({
        ...state, account: null, message: "", token: ""
      })),
      on(AccountActions.accountActivationSuccess, (state, { message } ) => ({
        ...state, message: message
      })),
    );