I am working on an application that is using ngrx/store 1.5 along with thunk middleware and I am attempting to move to ngrx/store 2.0 and ngrx/effects. I have a couple questions with regards to how to handle multiple related actions and/or effects.
I realize that the "mindset" for thunks vs effects is different and I'm trying to get my head around the differences. I have looked through the available example apps, and haven't found anything that seems to fit what I'm attempting, so perhaps I'm still approaching it completely wrong.
Scenario 1
Here is a side-effect to handle making the request to the server for a login:
@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))) ));
Given that initial side-effect, what would be the "correct" or "suggested" way to trigger navigation to a "home" screen upon successful login? This could also be generalized to simply triggering a sequence of actions or operations.
A few options I have considered:
(a) Another effect triggered by login success, that fires a subsequent action to trigger navigation?
@Effect navigateHome$: any = this.updates$ .whenAction(LoginActions.LOGIN_SUCCEEDED) .mapTo(this.actions.navigateHome());
(b) Another effect triggered by login success, that simply performs the navigation operation?
@Effect navigateHome$: any = this.updates$ .whenAction(LoginActions.LOGIN_SUCCEEDED) .do(this.navigateHome()) .filter(() => false);
(c) Concatenating an additional action to those emitted by the initial login effect? (sample obviously not quite correct, but gives the idea)
@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) Other?
Scenario 2
Consider a case where a number of requests need to be made in sequence, and as each request begins we want to update the "status" so that feedback can be provided to the user.
Example of a thunk for something along those lines:
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(); }) ... } }
Again, what would be the "correct" or "suggested" way to go about this in using the effects approach?
This one I'm more stuck on, not really sure what could be done other than adding some .do(this.store.dispatch(this.actions.updateStatus(...))
somehow...
You need to run this command npm install @ngrx/effects — save to install required dependencies. You have to define the actual services to make the API calls. Finally, you need to register all the effects with the EffectsModules and import this module into your main module or feature module.
Most effects are straightforward: they receive a triggering action, perform a side effect, and return an Observable stream of another action which indicates the result is ready. NgRx effects will then automatically dispatch that action to trigger the reducers and perform a state change.
'ofType' filters an Observable of Actions into an observable of the actions whose type strings are passed to it.
The answer for the navigation scenario is your b answer
@Effect navigateHome$: any = this.updates$ .whenAction(LoginActions.LOGIN_SUCCEEDED) .do(this.router.navigate('/home')) .ignoreElements();
Explanation: You react to the LOGIN_SUCCESS, and because the router doesn't return a new action, we need to stop the propagation of the stream, which we do by filtering everything.
If you forget to filter, the router returns undefined, which in turn will lead to the reducer to reduce an undefined value, which usually results in a null pointer when it tries to read the type
of the action
Another way to solve it is to use https://github.com/ngrx/router-store
Check the documentation on how to add router-store to your app.
The same effect will now look like this.
import { go } from '@ngrx/router-store'; @Effect navigateHome$: any = this.updates$ .whenAction(LoginActions.LOGIN_SUCCEEDED) .map(() => go(['/home']));
The go
action will dispatch a router action that the router reducer will pick up and trigger a route change.
Scenario 1
Consider whether or not navigateHome
should change the state. Also, whether or not this navigateHome
action is dispatched from other places to achieve the same thing. If so, returning an action is the way to go. Therefore, option A.
In some cases option B might make more sense. If navigateHome
only changes the route, it might be worth considering.
Side-note: you could use ignoreElements here instead of the filter(() => false)
.
Scenario 2
I would suggest chaining your actions here in multiple effects and giving feedback by modifying the state in the reducer for every action.
For example:
@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();
And give feedback by modifying the state:
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 }); } // ... } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With