ngrx effect 多次调用observer,但只调度了一个action
Posted
技术标签:
【中文标题】ngrx effect 多次调用observer,但只调度了一个action【英文标题】:ngrx effect calls observer multiple times, but only one action is dispatched 【发布时间】:2021-06-28 13:31:49 【问题描述】:最近几天我一直在努力解决这个问题的根源,但似乎无法弄清楚问题出在哪里。
情况: 我正在使用 Angular (11.2.6) 和 ngrx (11.0.1)。
问题: 一个动作(登录)的效果会永远执行一个可观察的(http post 请求)(不断发送 http post 请求)。即使在应用 rxjs 运算符“第一个”时,两个请求也会发送到后端。生成的操作 (loginSuccess) 只被调度一次,请求被执行多次。
代码: 我有一个登录操作,它需要用户名和密码作为参数。
actions/autentication.actions.ts:
import createAction, props from '@ngrx/store';
import Authentication from '../models/authentication';
import Credentials from '../models/credentials';
export const AuthenticationActions =
login: createAction("[Authentication] Login", props<Credentials>()),
refreshLogin: createAction("[Authentication] Refresh Login", props<Authentication>()),
loginSuccess: createAction("[Authentication] Login Success", props<Authentication>()),
loginFailure: createAction("[Authentication] Login Failed"),
refreshSuccess: createAction("Authentication Refresh Success", props<Authentication>()),
refreshFailure: createAction("Authentication Refresh Failed"),
logout: createAction("[Authentication] Logout"),
failed: createAction("[Authentication] Failed")
models/credentials.ts:
export interface Credentials
username: string
password: string
models/authentication.ts:
import User from "./user";
export interface Authentication
token: string
refreshToken: string
user: User
对于这个操作,我有一个效果(登录),它使用身份验证服务向后端发送登录请求。成功时它应该映射到登录成功操作,失败时映射到登录失败操作。这是无限循环的问题。
效果/身份验证.effects.ts:
import Injectable from '@angular/core';
import ActivatedRoute, ParamMap, Router from '@angular/router';
import Actions, createEffect, ofType from '@ngrx/effects';
import of from 'rxjs';
import catchError, exhaustMap, first, map, tap from 'rxjs/operators';
import AuthenticationService from 'src/services/authentication.service';
import SnackbarService from 'src/services/snackbar.service';
import AuthenticationActions from '../actions/authentication.actions';
import Authentication from '../models/authentication';
import Credentials from '../models/credentials';
@Injectable()
export class AuthenticationAffects
constructor(
private actions: Actions,
private authenticationService: AuthenticationService,
private router: Router,
private route: ActivatedRoute,
private snackbarService: SnackbarService
)
public login = createEffect(() =>
this.actions.pipe(
ofType(AuthenticationActions.login),
exhaustMap((credentials: Credentials) =>
return this.authenticationService.login(credentials.username, credentials.password).pipe(
map(authentication => (AuthenticationActions.loginSuccess(authentication))),
catchError(() => of(AuthenticationActions.loginFailure())))
)
)
)
public loginRefresh = createEffect(() => this.actions.pipe(
ofType(AuthenticationActions.refreshLogin),
exhaustMap((authentication: Authentication) =>
return this.authenticationService.refreshLogin(authentication.refreshToken).pipe(
first(),
map(authentication => (AuthenticationActions.refreshSuccess(authentication))),
catchError(() => of(AuthenticationActions.logout()))
)
)
))
public loginSuccess = createEffect(() =>
this.actions.pipe(
ofType(AuthenticationActions.loginSuccess),
tap(() =>
this.route.queryParamMap.subscribe((params: ParamMap) =>
let returnUrl = params.get("returnUrl")
if (returnUrl)
this.router.navigate([returnUrl])
else
this.router.navigate(["/app"])
)
)
),
dispatch: false
)
public loginFailure = createEffect(() =>
this.actions.pipe(
ofType(AuthenticationActions.loginFailure),
tap(() =>
this.snackbarService.showMessage("Login failed!")
)
),
dispatch: false
)
public logout = createEffect(() =>
this.actions.pipe(
ofType(AuthenticationActions.logout),
tap(() =>
this.router.navigate(["/login"])
)
),
dispatch: false
)
services/authentication.service.ts
import HttpClient from "@angular/common/http";
import Injectable from "@angular/core";
import Store from "@ngrx/store";
import Observable from 'rxjs';
import environment from "src/environments/environment";
import AppState from "src/redux/app-state";
@Injectable()
export class AuthenticationService
public constructor(private http: HttpClient, private store: Store<AppState>)
public login(name: string, password: string): Observable<any>
return this.http.post<any>(this.getServiceUrl() + "login",
name: name,
password: password
)
public refreshLogin(refreshToken: string): Observable<any>
return this.http.post<any>(this.getServiceUrl() + "refreshToken", ,
headers:
refreshToken
)
public authenticate(): Observable<any>
return this.http.get<any>(this.getServiceUrl() + "authenticate")
public getServiceUrl(): string
return environment.domain + "/" +
environment.services.authentication.uri
为了完成reducer。
reducers/authentication.reducer.ts:
import createReducer, on from '@ngrx/store';
import AuthenticationActions from '../actions/authentication.actions';
import Authentication from '../models/authentication';
import decode from 'jwt-decode'
export const initialAuthentication: Authentication = <Authentication>JSON.parse(localStorage.getItem("authentication")) || refreshToken: undefined, token: undefined, user: undefined
export const authenticationReducer = createReducer(initialAuthentication,
on(AuthenticationActions.loginSuccess, (state, authentication) =>
return authenticationSuccess(state, authentication)
),
on(AuthenticationActions.refreshSuccess, (state, authentication) =>
return authenticationSuccess(state, authentication)
),
on(AuthenticationActions.logout, (state) =>
localStorage.removeItem("authentication")
return Object.assign(, state, token: undefined, refreshToken: undefined, user: undefined )
)
);
function authenticationSuccess(state, authentication)
let authenticationState = token: authentication.token, refreshToken: authentication.refreshToken, user: decode(authentication.token)["user"]
localStorage.setItem("authentication", JSON.stringify(authenticationState))
return Object.assign(, state, authenticationState)
我已经尝试不加载 StoreDevToolsModule,这完全没有影响。在这一点上我真的很绝望,如果有任何意见,我将不胜感激!
编辑:为澄清起见,该操作仅在单击登录按钮一次时调度。
【问题讨论】:
【参考方案1】:正是因为那个exhuastMap
的作用。
在这里您可以找到文档 - https://www.learnrxjs.io/learn-rxjs/operators/transformation/exhaustmap
所以其他调用都被忽略了。
【讨论】:
问题不在于调用被忽略,问题在于可观察对象无限循环(从身份验证服务创建的)并且不会终止以上是关于ngrx effect 多次调用observer,但只调度了一个action的主要内容,如果未能解决你的问题,请参考以下文章
如何在ngrx/effect(redux-observable)中调度多个动作?
Angular ngrx + Firebase OAuth 操作已调用但效果不