使用正确账号密码登录时NgRx状态管理出现异常
1. 无限循环与Success Effect未触发的核心原因
你的loginSuccess$ Effect未配置dispatch: false,默认情况下createEffect会自动dispatch流中发出的内容。但该Effect仅包含tap操作(仅执行副作用,无返回值),导致它会发出undefined,NgRx会尝试dispatch这个无效的undefined action,进而触发Reducer无限执行,同时干扰了正常的loginSuccess action处理流程。
修复:给loginSuccess$添加{ dispatch: false }配置:
loginSuccess$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginSuccess), tap(({ loginResponse }) => { this.router.navigateByUrl('/register'); }) ), { dispatch: false } // 关键配置:不dispatch任何action );
2. Reducer状态结构不匹配
初始状态initialAuthState为LoginResponse类型,但该类型未包含isLoading、error字段,Reducer处理action时强行添加这些字段会导致状态类型不一致;同时loginSuccess的处理逻辑错误地将字符串类型的loginResponse(仅firstName)赋值给状态,无法正确更新完整用户信息。
修复:
- 创建包含状态字段的
AuthState类型,扩展原有用户信息结构:
export interface AuthState { user: LoginResponse; isLoading: boolean; error: string | null; } const initialAuthState: AuthState = { user: { username: "", firstName: "", token: "", lastName: "", photoUrl: "" }, isLoading: false, error: null };
- 修正Reducer逻辑,正确合并用户响应到状态:
export const _authReducer = createReducer(initialAuthState, on(AuthActions.loginRequest, state => ({...state, isLoading: true, error: null})), on(AuthActions.loginSuccess, (state, { loginResponse }) => ({ ...state, isLoading: false, user: { ...state.user, ...loginResponse } // 合并完整用户响应 })), on(AuthActions.loginFailure, (state, { error }) => ({...state, isLoading: false, error})) );
3. LoginSuccess Action的Payload类型错误
当前loginSuccess的payload仅传递了firstName字符串,无法承载完整用户信息。需修改Action,让它接收完整的LoginResponse类型。
修复login.action.ts:
import { LoginResponse } from "../../_models/LoginResponse"; export const loginSuccess = createAction( '[Login] Login Success', props<{ loginResponse: LoginResponse }>() // 改为完整的LoginResponse类型 )
同时修正Effect中loginSuccess的dispatch逻辑,传递完整响应:
map((loginResponse) => AuthActions.loginSuccess({ loginResponse })), // 传递完整响应而非仅firstName
4. 关键语法错误(Effect未被正确注入)
你的AuthEffects类漏写了@符号,Injectable()应改为@Injectable(),否则Angular无法识别并注入该Effect,这也是Effect不触发的重要原因。
5. 次要拼写错误
loginFailure的action类型拼写错误:[Login] Login Failuer → 改为[Login] Login Failure,避免后续调试混淆。
修正后的关键代码片段
login.action.ts
import { createAction, props } from "@ngrx/store"; import { LoginResponse } from "../../_models/LoginResponse"; export const loginRequest = createAction( '[Login] Login Request', props<{ credential: { emailId: string; password: string }}>() ) export const loginSuccess = createAction( '[Login] Login Success', props<{ loginResponse: LoginResponse }>() ) export const loginFailure = createAction( '[Login] Login Failure', props<{ error: string }>() )
login.effect.ts
import { inject, Injectable } from "@angular/core"; import { Actions, createEffect, ofType } from "@ngrx/effects"; import { AccountService } from "../../_services/account.service"; import { Router } from "@angular/router"; import * as AuthActions from '../actions/login.action'; import { catchError, exhaustMap, map, of, tap } from "rxjs"; @Injectable() export class AuthEffects { private actions$ = inject(Actions); private authService = inject(AccountService); private router = inject(Router); loginRequest$ = createEffect(() => this.actions$.pipe( ofType(AuthActions.loginRequest), exhaustMap((action) => this.authService .login({userName: action.credential.emailId, password: action.credential.password}) .pipe( map((loginResponse) => AuthActions.loginSuccess({ loginResponse })), catchError((error) => of(AuthActions.loginFailure({error: error.message}))) ) ) ) ); loginSuccess$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginSuccess), tap(() => { this.router.navigateByUrl('/register'); }) ), { dispatch: false } ); }
login.reducer.ts
import { Action, createReducer, on } from "@ngrx/store"; import { LoginResponse } from "../../_models/LoginResponse"; import * as AuthActions from '../actions/login.action'; export interface AuthState { user: LoginResponse; isLoading: boolean; error: string | null; } const initialAuthState: AuthState = { user: { username: "", firstName: "", token: "", lastName: "", photoUrl: "" }, isLoading: false, error: null }; export const _authReducer = createReducer(initialAuthState, on(AuthActions.loginRequest, state => ({...state, isLoading: true, error: null})), on(AuthActions.loginSuccess, (state, { loginResponse }) => ({ ...state, isLoading: false, user: { ...state.user, ...loginResponse } })), on(AuthActions.loginFailure, (state, { error }) => ({...state, isLoading: false, error})) ); export function authReducer(state: AuthState | undefined, action: Action) { console.log('State: ', state); console.log('Action: ', action); return _authReducer(state, action); }
内容的提问来源于stack exchange,提问作者Soumen

