Raz*_*ort 5 rxjs typescript redux ngrx angular
我在这里有点挣扎:我在 ngrx 效果中,我想对我的服务进行身份验证,并根据我的服务的回答,分派到操作以检索信息,然后才分派“YEAH LOGIN IS”类型的操作好的”
this is my code so far
this.actions$.pipe(
ofType(AuthActions.QuickLogin),
switchMap((action: any) =>
this.authService.postQuickLogin$(action.QuickVerifString).pipe(
switchMap(resultService => {
const props = {
username: resultService['username'],
token: resultService['token'],
isAuthenticated: true
}
this.localStorageService.setItem(AUTH_KEY, props)
return [
MoMenuActions.moMenuHttpGetListAction({ US_ID: props.username }),
UserActions.userHttpGetInfoAction({ US_ID: props.username }),
AuthActions.LoginSucceed(props)
]
}),
catchError(error => of(AuthActions.LoginError({ error })))
)
)
)
Run Code Online (Sandbox Code Playgroud)
这运行良好。直到我遇到在 momenuaction 和 useraction 中出现 http 错误的问题,并且我没有输入我的 catch 错误。这是正常的,因为 switchMap 取消了前一个 observable 而只取最后一个。我可以先映射然后映射然后映射 LoginSucceed 但在这种情况下我没有道具来调度我的 LoginSucceed
所以我不仅在寻找一种方法来做到这一点,而且在寻找“好的”/正确的方法来做到这一点。
如果有人有任何解决方案和原因的解释?
请看一看Victor Savkin关于NgRx模式和技术的精彩文章。特别是拆分器和聚合器模式:
分路器
拆分器将一个动作映射到一系列动作,即,它拆分一个动作。
Run Code Online (Sandbox Code Playgroud)class TodosEffects { constructor(private actions: Actions) {} @Effect() addTodo = this.actions.typeOf('REQUEST_ADD_TODO').flatMap(add => [ {type: 'ADD_TODO', payload: add.payload}, {type: 'LOG_OPERATION', payload: {loggedAction: 'ADD_TODO', payload: add.payload}} ]); }这与将一个方法拆分为多个方法的原因完全相同:我们可以独立地测试、装饰、监控每个动作。
聚合器
聚合器将一系列动作映射到单个动作。
Run Code Online (Sandbox Code Playgroud)class TodosEffects { constructor(private actions: Actions) {} @Effect() aggregator = this.actions.typeOf(‘ADD_TODO’).flatMap(a => zip( // note how we use a correlation id to select the right action this.actions.filter(t => t.type == 'TODO_ADDED' && t.payload.id === a.payload.id).first(), this.actions.filter(t => t.type == ‘LOGGED' && t.payload.id === a.payload.id).first() ) ).map(pair => ({ type: 'ADD_TODO_COMPLETED', payload: {id: pair[0].payload.id, log: pair[1].payload} })); }聚合器不像拆分器那么常见,因此 RxJs 没有附带实现它的运算符。这就是为什么我们必须添加一些样板来自己做。但是总是可以引入一个自定义的 RxJS 操作符来帮助解决这个问题。
...
基于此,我们的想法是使效果尽可能小,以便它们可以轻松测试和重用。
例如,让我们假设有一个SIGN_IN动作涉及:
GET_TOKEN=>GET_TOKEN_SUCCESS或GET_TOKEN_FAIL)GET_DETAILS=>GET_DETAILS_SUCCESS或GET_DETAILS_FAIL)一旦两个动作都成功,我们就可以调度SIGN_IN_SUCCESS动作。但是如果它们中的任何一个失败了,我们就需要调度SIGN_IN_FAIL动作。
操作如下所示:
// Sign In
export const SIGN_IN = 'Sign In';
export const SIGN_IN_FAIL = 'Sign In Fail';
export const SIGN_IN_SUCCESS = 'Sign In Success';
export class SignIn implements Action {
readonly type = SIGN_IN;
constructor(public payload: { email: string; password: string; correlationParams: CorrelationParams }) {}
}
export class SignInFail implements Action {
readonly type = SIGN_IN_FAIL;
constructor(public payload: { message: string }) {}
}
export class SignInSuccess implements Action {
readonly type = SIGN_IN_SUCCESS;
constructor(public payload: { tokenDetails: Token; userDetails: User; }) {}
}
// Get Token
export const GET_TOKEN = 'Get Token';
export const GET_TOKEN_FAIL = 'Get Token Fail';
export const GET_TOKEN_SUCCESS = 'Get Token Success';
export class GetToken implements Action {
readonly type = GET_TOKEN;
constructor(public payload: { email: string; password: string; correlationParams: CorrelationParams }) {}
}
export class GetTokenFail implements Action {
readonly type = GET_TOKEN_FAIL;
constructor(public payload: { message: string; correlationParams: CorrelationParams }) {}
}
export class GetTokenSuccess implements Action {
readonly type = GET_TOKEN_SUCCESS;
constructor(public payload: { tokenDetails: Token; correlationParams: CorrelationParams }) {}
}
// Get Details
export const GET_DETAILS = 'Get Details';
export const GET_DETAILS_FAIL = 'Get Details Fail';
export const GET_DETAILS_SUCCESS = 'Get Details Success';
export class GetDetails implements Action {
readonly type = GET_DETAILS;
constructor(public payload: { correlationParams: CorrelationParams }) {}
}
export class GetDetailsFail implements Action {
readonly type = GET_DETAILS_FAIL;
constructor(public payload: { message: string; correlationParams: CorrelationParams }) {}
}
export class GetDetailsSuccess implements Action {
readonly type = GET_DETAILS_SUCCESS;
constructor(public payload: { userDetails: User; correlationParams: CorrelationParams }) {}
}
Run Code Online (Sandbox Code Playgroud)
请注意correlationParams: CorrelationParams有效载荷的部分。该correlationParams对象允许我们知道不同的操作,如SIGN_IN,GET_TOKEN和GET_DETAILS是否与相同的登录过程相关(以便能够应用拆分器和聚合器技术)。
该类(以及将在效果中使用的运算符)的定义如下:
// NgRx
import { Action } from '@ngrx/store';
// UUID generator
// I'm using uuid as the id but you can use anything else if you want!
import { v4 as uuid } from 'uuid';
export class CorrelationParams {
public correlationId?: string;
public static create(): CorrelationParams {
const correlationParams: CorrelationParams = {
correlationId: uuid(),
};
return correlationParams;
}
public static fromAction(action: AggregatableAction): CorrelationParams {
return action && action.payload && action.payload.correlationParams
? action.payload.correlationParams
: null;
}
}
export type AggregatableAction = Action & { payload?: { correlationParams?: CorrelationParams } };
export const filterAggregatableAction = (
sourceAction: AggregatableAction,
anotherAction: AggregatableAction,
) => {
const sourceActionCorrelationParams = CorrelationParams.fromAction(sourceAction);
const anotherActionCorrelationParams = CorrelationParams.fromAction(anotherAction);
return (
sourceActionCorrelationParams &&
anotherActionCorrelationParams &&
sourceActionCorrelationParams.correlationId === anotherActionCorrelationParams.correlationId
);
};
Run Code Online (Sandbox Code Playgroud)
因此,在分发SIGN_IN操作时,我们需要将其添加correlationParams到有效负载中,如下所示:
public signIn(email: string, password: string): void {
const correlationParams = CorrelationParams.create();
this.store$.dispatch(
new fromUserActions.SignIn({ email, password, correlationParams }),
);
}
Run Code Online (Sandbox Code Playgroud)
现在是有趣的部分,效果!
// Splitter: SIGN_IN dispatches GET_TOKEN and GET_DETAILS actions
@Effect()
signIn$ = this.actions$.pipe(
ofType(fromUserActions.SIGN_IN),
flatMap((action: fromUserActions.SignIn) => {
const { email, password, correlationParams } = action.payload;
return [
new fromUserActions.GetToken({ email, password, correlationParams }),
new fromUserActions.GetDetails({ correlationParams }),
];
}),
);
// Gets the token details from the API
@Effect()
getToken$ = this.actions$.pipe(
ofType(fromUserActions.GET_TOKEN),
switchMap((action: fromUserActions.GetToken) => {
const { email, password, correlationParams } = action.payload;
return this.userService.getToken(email, password).pipe(
map(tokenDetails => {
return new fromUserActions.GetTokenSuccess({ tokenDetails, correlationParams });
}),
catchError(error => {
const message = ErrorHelpers.getErrorMessageFromHttpErrorResponse(error);
return of(new fromUserActions.GetTokenFail({ message, correlationParams }));
}),
);
}),
);
// Gets the user details from the API
// This action needs to wait for the access token to be obtained since
// we need to send the access token in order to get the user details
@Effect()
getDetails$ = this.actions$.pipe(
ofType(fromUserActions.GET_DETAILS),
concatMap((action: fromUserActions.GetDetails) =>
of(action).pipe(
// Use combineLatest so we can wait for the token to be
// available before getting the details of the user
combineLatest(
this.store$.pipe(
select(fromUserSelectors.getAccessToken),
filter(accessToken => !!accessToken),
take(1),
),
),
),
),
switchMap(([action, _]) => {
const { correlationParams } = action.payload;
return this.userService.getDetails().pipe(
map(userDetails => {
return new fromUserActions.GetDetailsSuccess({ userDetails, correlationParams });
}),
catchError(error => {
const message = ErrorHelpers.getErrorMessageFromHttpErrorResponse(error);
return of(new fromUserActions.GetDetailsFail({ message, correlationParams }));
}),
);
}),
);
// Aggregator: SIGN_IN_SUCCESS can only be dispatched if both GET_TOKEN_SUCCESS and GET_DETAILS_SUCCESS were dispatched
@Effect()
aggregateSignIn$ = this.actions$.pipe(
ofType(fromUserActions.SIGN_IN),
switchMap((signInAction: fromUserActions.SignIn) => {
// GetTokenSuccess
let action1$ = this.actions$.pipe(
ofType(fromUserActions.GET_TOKEN_SUCCESS),
filter((getTokenAction: fromUserActions.GetTokenSuccess) => {
return filterAggregatableAction(signInAction, getTokenAction);
}),
first(),
);
// GetDetailsSuccess
let action2$ = this.actions$.pipe(
ofType(fromUserActions.GET_DETAILS_SUCCESS),
filter((getDetailsAction: fromUserActions.GeDetailsSuccess) => {
return filterAggregatableAction(signInAction, getDetailsAction);
}),
first(),
);
// failAction means that something went wrong!
let failAction$ = this.actions$.pipe(
ofType(
fromUserActions.GET_TOKEN_FAIL,
fromUserActions.GET_DETAILS_FAIL,
),
filter(
(
failAction:
| fromUserActions.GetTokenFail
| fromUserActions.GetDetailsFail
) => {
return filterAggregatableAction(signInAction, failAction);
},
),
first(),
switchMap(failAction => {
return throwError(failAction.payload.message);
}),
);
// Return what happens first between all the sucess actions or the first error action
return race(forkJoin([action1$, action2$]), failAction$);
}),
map(([getTokenSuccess, getDetailsSuccess]) => {
const { tokenDetails } = getTokenSuccess.payload;
const { userDetails } = getDetailsSuccess.payload;
return new fromUserActions.SignInSuccess({ tokenDetails, userDetails });
}),
catchError(() => {
return of(new fromUserActions.SignInFail({ message: ErrorMessage.Unknown }));
}),
);
Run Code Online (Sandbox Code Playgroud)
我不是 NgRx / RxJS 的专家,所以可能有更好的方法来处理这个问题,但要记住的重要一点是模式背后的想法,而不是这个代码片段。
| 归档时间: |
|
| 查看次数: |
3869 次 |
| 最近记录: |