web-dev-qa-db-ja.com

ngrxチェーンアクション/エフェクト-例:ログインしてからナビゲートする

私はngrxとReduxスタイルのアーキテクチャに不慣れで、アクション/エフェクトをチェーンする方法を理解するのに問題があります。 1つの例は、ユーザーがログインした後にアクションを実行するなどの基本的な機能を実現することです。ここでの私の主な問題は、ユーザーがログインした後に実行するアクションが、アプリケーションの現在の状態によって異なることです。ユーザーはどこにいてもかまいません。ログインプロンプト/ページが表示されたときにアプリケーションで。

ユーザーがログインすると発生するハードコード効果を確認した例。私のシナリオでは、これは上記のようにうまく適合せず、常に同じアクションが発生することを望んでいません。

ログインコンポーネントを含むホームページのサンプルコードを次に示します。このシナリオでは、ユーザーがログインした後、ユーザーを「/ 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でアクションを実行するが、より高いレベルでフィルタリングされる多くのエフェクトを書き込む
  • ストアでイベントをリッスンしてコンポーネント固有の効果を記述します(これは可能/悪い習慣ですか?)
  • 現在のログインステータスに関するストアからデータを読み取り、コンポーネントでこれに基づいて動作します。ただし、このロジックはすぐに実行されるため、目的の効果が得られない可能性があります。

誰かが私を正しい道に向けることができますか/これがどのように解決されるべきかの例を提示できますか?

11
dannym

エフェクトは、アクションの配列に対して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」アクションを作成する必要はありません。redirectcloseModalを渡すことができます。

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

このgithubの問題で、1つの@Effect()から複数のアクションをディスパッチする についてもう少し読むことができます。

編集:コメントに関しては、ログインを停止したくなるのはルートの変更だけではなく、モーダルを閉じるなど、何でもかまいません。それを監視する一般的な方法はありません。 runLoginActions booleanなどをストアに追加し、ログイン要求が戻ってきた後にストアからそのデータを読み取ることができます。

次に、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のレデューサーでは、runLoginActions = trueを設定する必要があり、ルートが変更されたとき、またはコンポーネントが破壊されたときにのみオフになるように、デフォルトでtrueとして開始する必要があることに注意してください。

これは一般的な解決策であり、アクションを自分で追加したり、レデューサーを更新したりする必要があることに注意してください。

これは手動のプロセスですが、アクションを実行したくない場合はかなり具体的であるため、この方法で実行する必要があります。

10
Adam