【发布时间】: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,这完全没有影响。在这一点上我真的很绝望,如果有任何意见,我将不胜感激!
编辑:为澄清起见,该操作仅在单击登录按钮一次时调度。
【问题讨论】:
标签: angular typescript redux rxjs ngrx