ngrx链接动作/效果 - 例如登录然后导航

时间:2017-02-21 16:45:33

标签: angular ngrx ngrx-effects

我是ngrx和Redux风格架构的新手,我在理解如何链接动作/效果方面遇到了问题。一个例子是实现基本功能,例如在用户登录后采取行动。我的核心斗争是在用户登录后要采取的行动将根据应用程序的当前状态而变化 - 用户可能在任何地方在提交登录提示/页面的应用程序中。

我见过的任何一些例子,一旦用户登录就会发生硬编码效果。在我的场景中,这不符合上述说法,我并不总是希望同样的动作发生。

以下是包含登录组件的主页的一些示例代码。在这种情况下,我希望用户被重定向到' / buy'一旦他们登录。

@Component(..)
export class HomePageComponent {
    constructor(private store: Store<any>) {
    }

    public onLoginSubmitted(username, password) {
        this.store.dispatch(new loginActions.Login({username, password}));
        // once this has happened successfully navigate to /buy    
    }

}

示例效果

@Injectable()
export class LoginEffects {

    ......     

    @Effect()
    login$ = this.actions$
        .ofType(loginActions.ActionTypes.LOGIN)
        .switchMap((data) => {
            return this.loginService.login(data.payload)
                .map(() => new loginActions.LoginSuccess({loggedIn: true, isLoggingIn: false}))
                .catch((error) => {
                    return of(new loginActions.LoginError({loggedIn: false, isLoggingIn: false}));
                });
        });
    ......
}

我对如何解决这个问题有几点想法 - 但他们都不是正确的。其中包括

  • 使用登录有效负载传递数据以确定下一步采取的操作
  • 写一些在LoginSuccess上采取行动但在某个更高级别过滤的效果
  • 通过收听商店中的事件来编写特定于组件的效果(这是可能/不好的做法吗?)
  • 从商店中读取有关当前登录状态的数据,并在组件中对此进行操作。但是这个逻辑会立即运行,这可能没有预期的效果

有人能指出我应该如何解决这个问题的正确道路/现有例子吗?

1 个答案:

答案 0 :(得分:10)

效果可以使用mergeMap对一系列操作触发多个操作。我通常会做的是,传递我希望登录效果也可以发送的其他操作。

@Component(..)
export class HomePageComponent {
    constructor(private store: Store<any>) { }

    public onLoginSubmitted(username, password) {
        this.store.dispatch(new loginActions.Login({
            username, 
            password, 
            onCompleteActions: [new loginActions.Redirect({ route: '/buy' })] }));
    }
}

然后登录效果将使用mergeMap调度任何传入的操作。您仍然需要为每个操作创建一个效果,但现在您可以构建更多可重用的操作,例如重定向操作。

@Injectable()
export class LoginEffects {

    ......     

    @Effect()
    login$ = this.actions$
        .ofType(loginActions.ActionTypes.LOGIN)
        .switchMap((data) => {
            let mappedActions = [new loginActions.LoginSuccess({loggedIn: true, isLoggingIn: false})];
            if (data.payload.onCompleteActions)
                mappedActions = mappedActions.concat(data.payload.onCompleteActions);

            return this.loginService.login(data.payload)
                .mergeMap(mappedActions)
                .catch((error) => {
                    return of(new loginActions.LoginError({loggedIn: false, isLoggingIn: false}));
                });
        });

    ......
}

我喜欢这个解决方案的是它可以在未来扩展,因为任何动作都可以传入,并且没有开关或if-else树来决定做什么。您还可以传递多个操作,因此无需构建“redirectAndCloseModal”&#39;行动,您可以传递redirect以及closeModal

this.store.dispatch(new loginActions.Login({
    username, 
    password, 
    onCompleteActions: [new loginActions.Redirect({ route: '/buy' }), new modalActions.CloseModal()] }));

您可以阅读更多关于dispatching multiple Actions from one @Effect() at this github issue.

的信息

编辑:关于您的评论,不仅仅是路线更改可能会让您想要停止登录,也可能是任何事情,例如关闭模式,以及&#39 ;没有通用的方式来监视它。您可以在商店中添加一些内容,例如runLoginActions布尔值,然后在登录请求返回后从商店中读取该数据。

然后在您的login$效果中,您可以从商店中读取该内容,以决定是否运行其他操作。

@Effect()
login$: Observable<Action> = this.actions$
    .ofType(auth.actionTypes.LOGIN)
    .map((action: auth.LoginAction) => action.payload)
    .switchMap((user) => {
        let successAction = new loginActions.LoginSuccess({loggedIn: true, isLoggingIn: false});
        let mappedActions = [successAction];
        if (data.payload.onCompleteAction)
            mappedActions.push(data.payload.onCompleteAction);

        return this.authService.login(user.username, user.password)
            .withLatestFrom(this.store)
            .mergeMap(([result, store]) => store.runLoginActions ?
                [mappedActions] :
                [successAction]
            )
            .catch((err) => {
                try {
                    return of(new auth.NetworkFailureAction(err[0].code));
                } catch (e) {
                    throw `Array not in the expected format of [{ code: "", message: "" }]`;
                }
            });
    });

请注意,在loginSuccess的reducer中,您应该设置runLoginActions = true,默认情况下它应该为true,只有在路径更改或组件被销毁时才会关闭

请注意,这是一个通用解决方案,您需要自行添加操作,更新缩减器等。

这不仅仅是一个手动过程,但由于您不想执行操作的案例非常具体,因此可能必须以这种方式完成。