web-dev-qa-db-fra.com

Comment effectuer plusieurs opérations / effets / actions associés avec ngrx / effets

Je travaille sur une application qui utilise ngrx/store 1.5 avec un middleware thunk et j'essaie de passer à ngrx/store 2.0 et ngrx/effects. J'ai quelques questions sur la façon de gérer plusieurs actions et/ou effets connexes.

Je me rends compte que la "mentalité" pour les thunks vs les effets est différente et j'essaie de comprendre les différences. J'ai regardé les exemples d'applications disponibles, et je n'ai trouvé rien qui semble correspondre à ce que j'essaie, alors peut-être que je m'en approche toujours complètement.

Scénario 1

Voici un effet secondaire pour gérer la demande de connexion au serveur:

@Effect login$: any = this.updates$
    .whenAction(LoginActions.LOGIN)
    .map(toPayload)
    .switchMap(payload => 
        this.loginService.login(payload.user, payload.password)
            .map(result => this.actions.loginSuccess(value))
            .catch((error) => Observable.of(this.loginError(error)))
));

Compte tenu de cet effet secondaire initial, quelle serait la façon "correcte" ou "suggérée" de déclencher la navigation vers un écran "d'accueil" après une connexion réussie? Cela pourrait également être généralisé pour simplement déclencher une séquence d'actions ou d'opérations.

Quelques options que j'ai envisagées:

(a) Un autre effet déclenché par la réussite de la connexion, qui déclenche une action ultérieure pour déclencher la navigation?

@Effect navigateHome$: any = this.updates$
    .whenAction(LoginActions.LOGIN_SUCCEEDED)
    .mapTo(this.actions.navigateHome());

(b) Un autre effet déclenché par le succès de la connexion, qui effectue simplement l'opération de navigation?

@Effect navigateHome$: any = this.updates$
    .whenAction(LoginActions.LOGIN_SUCCEEDED)
    .do(this.navigateHome())
    .filter(() => false);

(c) Concaténer une action supplémentaire à celles émises par l'effet de connexion initial? (échantillon évidemment pas tout à fait correct, mais donne l'idée)

@Effect login$: any = this.updates$
    .whenAction(LoginActions.LOGIN)
    .map(toPayload)
    .switchMap(password => Observable.concat(
        this.loginService.login(passcode)
            .map(result => this.actions.loginSuccess(value))
            .catch((error) => Observable.of(this.loginError(error))),
        Observable.of(this.actions.navigateHome())
    ));

(d) Autre?

scénario 2

Considérons un cas où un certain nombre de demandes doivent être effectuées en séquence, et au fur et à mesure que chaque demande commence, nous voulons mettre à jour le "statut" afin que des commentaires puissent être fournis à l'utilisateur.

Exemple de thunk pour quelque chose dans ce sens:

multiphaseAction() {
    return (dispatch) => {
        dispatch(this.actions.updateStatus('Executing phase 1');
        this.request1()
            .flatMap(result => {
                dispatch(this.actions.updateStatus('Executing phase 2');
                return this.request2();
            })
            .flatMap(result => {
                dispatch(this.actions.updateStatus('Executing phase 3');
                return this.request3();
            })
            ...
    }
}

Encore une fois, quelle serait la façon "correcte" ou "suggérée" de procéder à cet égard en utilisant l'approche des effets?

Celui-ci, je suis plus coincé, pas vraiment sûr de ce qui pourrait être fait autre que l'ajout de .do(this.store.dispatch(this.actions.updateStatus(...)) d'une manière ou d'une autre ...

34

La réponse pour le scénario de navigation est votre réponse b

@Effect navigateHome$: any = this.updates$
    .whenAction(LoginActions.LOGIN_SUCCEEDED)
    .do(this.router.navigate('/home'))
    .ignoreElements();

Explication: vous réagissez à LOGIN_SUCCESS, et parce que le routeur ne renvoie pas de nouvelle action, nous devons arrêter la propagation du flux, ce que nous faisons en filtrant tout.

Si vous oubliez de filtrer, le routeur renvoie undefined, ce qui à son tour conduira le réducteur à réduire une valeur indéfinie, ce qui entraîne généralement un pointeur nul lorsqu'il essaie de lire le type de l'action

Une autre façon de le résoudre est d'utiliser https://github.com/ngrx/router-store

Consultez la documentation sur la façon d'ajouter un routeur-magasin à votre application.

Le même effet ressemblera maintenant à ceci.

import { go } from '@ngrx/router-store';

@Effect navigateHome$: any = this.updates$
    .whenAction(LoginActions.LOGIN_SUCCEEDED)
    .map(() => go(['/home']));

L'action go enverra une action de routeur que le réducteur de routeur prendra et déclenchera un changement de route.

17
Leon Radley

Scénario 1

Déterminez si navigateHome doit ou non modifier l'état. De plus, que cette action navigateHome soit envoyée depuis d'autres endroits ou non pour obtenir la même chose. Si c'est le cas, retourner une action est la voie à suivre. Par conséquent, l'option A.

Dans certains cas, l'option B pourrait avoir plus de sens. Si navigateHome ne modifie que l'itinéraire, cela peut valoir la peine d'être considéré.

Remarque: vous pouvez utiliser ignoreElements ici au lieu de filter(() => false).

Scénario 2

Je suggérerais d'enchaîner vos actions ici en plusieurs effets et de donner un feedback en modifiant l'état dans le réducteur pour chaque action.

Par exemple:

@Effect() trigger$: Observable<Action> = this.updates$
    .whenAction(Actions.TRIGGER)
    .do(() => console.log("start doing something here"))
    .mapTo(Actions.PHASE1);

@Effect() phase1$: Observable<Action> = this.updates$
    .whenAction(Actions.PHASE1)
    .do(() => console.log("phase 1"))
    .mapTo(Actions.PHASE2);

@Effect() phase2$: Observable<Action> = this.updates$
    .whenAction(Actions.PHASE2)
    .do(() => console.log("phase 2"))
    .ignoreElements();

Et donnez votre avis en modifiant l'état:

function reducer(state = initialState, action: Action): SomeState {
    switch (action.type) {
        case Actions.TRIGGER: {
            return Object.assign({}, state, {
                triggered: true
            });
        }
        case Actions.PHASE1: {
            return Object.assign({}, state, {
                phase1: true
            });
        }
        case Actions.PHASE2: {
            return Object.assign({}, state, {
                phase1: false,
                phase2: true
            });
        }
        // ...
    }
}
2
Laurens

Scénario 1

les options A et B sont toutes les deux bonnes, je pense, mais peut-être que l'option A est un peu trop juste pour la navigation! Vous pouvez également utiliser directement le routeur dans votre LoginActions.LOGIN si vous considérez la navigation comme faisant partie de cet effet secondaire.

scénario 2

Il n'y a rien de mal à enchaîner @Effects. Envoyez une action (SET_1_REQUEST) qui déclenche votre effet A. l'effet A renvoie une action (SET_1_SUCCESS) qui est récupérée par votre réducteur (vous pouvez maintenant afficher ce statut à l'utilisateur) et récupérée par l'effet B.

J'espère que cela a du sens.

0
littleStudent