javascriptangulartypescriptngrxeffects

Angular Ngrx - Effects not be called when dispatch Actions


I am newbie with Ngrx and this is package.json file for my project, I am trying to implement dispatch login action for my project

{
  "name": "shop-saleman",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^11.0.5",
    "@angular/common": "^11.0.5",
    "@angular/compiler": "^11.0.5",
    "@angular/core": "^11.0.5",
    "@angular/forms": "^11.0.5",
    "@angular/localize": "^11.0.5",
    "@angular/platform-browser": "^11.0.5",
    "@angular/platform-browser-dynamic": "^11.0.5",
    "@angular/router": "^11.0.5",
    "@ng-bootstrap/ng-bootstrap": "9.1.3",
    "@ngrx/effects": "^11.0.0",
    "@ngrx/store": "^11.0.0",
    "@types/socket.io-client": "^3.0.0",
    "bootstrap": "4.5.0",
    "ngx-cookie-service": "11.0.2",
    "pretty-ms": "^7.0.1",
    "rxjs": "^6.6.3",
    "rxjs-compat": "^6.0.0",
    "socket.io-client": "^4.5.1",
    "tslib": "^2.0.3",
    "zone.js": "^0.10.3"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^0.1100.4",
    "@angular/cli": "^11.0.4",
    "@angular/compiler-cli": "^11.0.5",
    "@types/jasmine": "~3.6.0",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "^12.19.9",
    "codelyzer": "^6.0.1",
    "jasmine-core": "~3.6.0",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "~5.1.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.0.3",
    "karma-jasmine": "~4.0.0",
    "karma-jasmine-html-reporter": "^1.5.4",
    "protractor": "~7.0.0",
    "ts-node": "~8.3.0",
    "tslint": "~6.1.0",
    "typescript": "~4.0.2"
  }
}

this is src/store/index.ts for store

import { User } from "src/app/model/user.model";

export type AppState = {
  user: User;
};

this is src/store/actions/user.action.ts for user 's actions

import { createAction, props } from "@ngrx/store";
import { User } from "src/app/model/user.model";

export const Types = {
  LOGIN_PWD: "[USER] login with password",
  LOGIN_SUC: "[USER] login successfully",
  LOGIN_FAILED: "[USER] login failed",
};

export const lgPwd = createAction(
  Types.LOGIN_PWD,
  props<{ username: string; password: string }>()
);

export const lgSuc = createAction(
  Types.LOGIN_SUC,
  props<{ user: User; token: string }>()
);

export const lgFail = createAction(Types.LOGIN_FAILED, props<{ error: any }>());

this is src/store/effects/user.effects.ts for actions when dispatching they will call, I add console.log to display username and password when it was passed:

import { Actions, ofType, createEffect, Effect } from "@ngrx/effects";
import { Injectable } from "@angular/core";
import { Store } from "@ngrx/store";
import { AppState } from "..";
import { UserService } from "src/app/services/auth.service";
import { lgPwd } from "../actions/user.actions";
import { catchError, map, switchMap } from "rxjs/operators";
import { from } from "rxjs";

@Injectable()
export class UserEffect {
  constructor(
    private action: Actions,
    private store: Store<AppState>,
    private user_service: UserService
  ) {}

  lgPwdCall = createEffect(
    () => {
      return this.action.pipe(
        ofType(lgPwd),
        switchMap(({username, password}) => {
          console.log('EFFECT ->', username, password)
          return from(
            this.user_service.lgPwd(username, password)
          ).pipe(
            map((result) => {
              console.log("success", result);
            }),
            catchError((err): any => {
              console.log("err", err);
            })
          );
        })
      );
    },
    { dispatch: false }
  );
}

this is src/store/reducers/user.reducers.ts is defined the reducers for actions, I add console.log to display username and password:

import { createReducer, on } from "@ngrx/store";
import { lgPwd } from "../actions/user.actions";

const initialState = {
  currentUser: null,
  token: "",
};

export const userReducer = createReducer(
  initialState,
  on(lgPwd, (state, { username, password }) => {
    console.log("REDUCER ->", username, password);
    state.currentUser = { username, password };
    return state;
  })
);

this is the selectors src/store/selectors/user.selectors.ts:

import { createSelector } from "@ngrx/store";
import { AppState } from "..";

export const userState = (state: AppState) => state.user;

export const selectCurrentUser = createSelector(userState, (user) => user);

this is the interface for type User (incase you need) src/app/model/user.model.ts:

export interface User {
  addressList: string[];
  avatar: string;
  cardNumber: string;
  email: string;
  isActived: boolean;
  mediaList: Media[];
  phoneNumber: string;
  role: string;
  username: string;
  zipCode: string;
  _id: string;
}

export interface Media {
  _id: string;
  mimetype: string;
  filename: string;
}

the action is dispatch in src/app/pages/login/login.component.ts in function login:

import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Store } from "@ngrx/store";
import { CookieService } from "ngx-cookie-service";
import { AppState } from "src/store";
import { lgPwd } from "src/store/actions/user.actions";
import { UserService } from "../../services/auth.service";

@Component({
  selector: "app-login",
  templateUrl: "./login.component.html",
  styleUrls: ["./login.component.scss"],
})
export class LoginComponent implements OnInit {
  email: string = "";
  pwd: string = "";
  isLogin: boolean = false;
  visiblePwd: boolean = false;

  constructor(
    private api: UserService,
    private route: Router,
    private cookie_service: CookieService,
    private store: Store<AppState>
  ) {}

  ngOnInit(): void {}

  login() {
    this.isLogin = true;
    this.store.dispatch(lgPwd({ username: this.email, password: this.pwd }));
  }

  togglePwd() {
    return (this.visiblePwd = !this.visiblePwd);
  }
}

and this is what I got when I toggle inspect on browser, as you can see, The reducer got correct username and password but It cannot see any console.log from the effect that the effects has not been called yet

enter image description here

please help me, it means a lot, thanks for your time and have a good day


Solution

  • Problem originates from state.currentUser = { username, password };, here you are trying to assign { username, password } but currentUser is in ngrx state, and this object state is read only.

    Instead create a new action, which on login success, sets the logged in user (probably result?) somewhere in the state.

    map((result) => {
                  console.log("success", result);
        // create new action here OnLoginSuccess
                }),