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流。
修复步骤:
- 打开根模块
AppModule(或对应Store特性模块) - 在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
相关产品推荐
相关产品推荐

