You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular NgRx派发Action后Effects未触发问题求助

NgRx 登录Action派发后Effect未触发问题

问题现象

项目使用Angular 11 + NgRx 11实现登录逻辑,在登录组件派发密码登录Action后,Reducer可以正常执行并打印传入的用户名、密码参数,但是绑定的Effect始终未触发,Effect内的日志无任何输出。

现有项目代码

项目依赖(package.json)

{
  "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"
  }
}

根Store定义(src/store/index.ts)

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

export type AppState = {
  user: User;
};

用户Action定义(src/store/actions/user.action.ts)

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 }>());

用户Effect定义(src/store/effects/user.effects.ts)

import { Actions, ofType, createEffect } 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 }
  );
}

用户Reducer定义(src/store/reducers/user.reducers.ts)

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;
  })
);

用户选择器定义(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);

User模型定义(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;
}

登录组件代码(src/app/pages/login/login.component.ts)

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);
  }
}

控制台现象

控制台输出截图
Reducer日志正常打印,Effect无日志输出。

问题原因与修复方案

核心原因:未正确注册NgRx Effects模块

Effect不触发的绝大多数场景都是没有在Angular模块中导入EffectsModule并声明编写的UserEffect类,NgRx不会自动扫描未注册的Effect,自然无法订阅Action流。
修复步骤:

  1. 打开根模块AppModule(或对应Store特性模块)
  2. 在imports数组中添加Effects模块注册代码,同时确认Store模块已经正确注册userReducer:
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { userReducer } from './store/reducers/user.reducers';
import { UserEffect } from './store/effects/user.effects';

@NgModule({
  imports: [
    // 其他业务模块...
    StoreModule.forRoot({ user: userReducer }),
    // 根模块用forRoot注册Effect,特性模块用forFeature
    EffectsModule.forRoot([UserEffect])
  ]
})
export class AppModule {}

其他需要同步修复的代码问题

  • State结构不匹配:定义的AppState里user字段是User类型,但实际Reducer返回的结构是{currentUser: User | null, token: string},需要修正AppState的类型定义,避免选择器、静态类型校验出错。
  • Reducer直接修改原State:NgRx要求State是不可变的,不能直接给state.currentUser赋值,必须通过展开运算符返回全新的state对象,否则会导致组件状态变更检测失效。
  • Effect流未正确返回值:当前Effect里map和catchError操作符只是打印日志,没有返回合法的Observable,即使Effect触发,第一次执行后整个流就会终止,后续Action无法再监听。如果需要Effect自动派发后续Action,要在map里返回成功Action、catchError里用of()包装返回错误Action,同时去掉{dispatch: false}配置;如果不需要自动派发Action,也要保证catchError返回Observable,避免流中断。
  • 文件名一致性检查:标注的Action文件名是user.action.ts(单数),但代码里import路径写的是user.actions(复数),如果编译没有报错说明是笔误,如果编译报错需要修正import路径和实际文件名保持一致。

内容的提问来源于stack exchange,提问作者Tam Do

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 07:42:15