5
votes

I have an Angular (2) app with four ngrx actions:

  • START
    • Not processed by the reducer (no state change)
    • ngrx Effect calls an async task and maps to SUCCESS or ERROR
  • SUCCESS
    • Processed by the reducer
    • ngrx Effect maps to ADVANCE
  • ADVANCE
    • Not processed by the reducer
    • ngrx Effect navigates to a different route
  • ERROR
    • Processed by the reducer
    • No Effect

The problem is that the Effect that catches ADVANCE seems to run before the reducer that processes SUCCESS

Here's the Effects code:

@Effect() start$ = this.actions$
    .ofType('START')
    .map(toPayload)
    .switchMap(input => doAsyncTask(input)
        .map(result => ({type: 'SUCCESS', payload: result}))
        .catch(error => ({type: 'ERROR', payload: error})));

@Effect() success$ = this.actions$
    .ofType('SUCCESS')
    .map(() => ({type: 'ADVANCE'}));

@Effect({dispatch: false}) advance$ = this.actions$
    .ofType('ADVANCE')
    .withLatestFrom(this.store$.select(state => state.route))
    .map(action_route => action_route[1])
    .do(route => this.router.navigate([route.foo.bar]));

The error that I am getting is Cannot read property 'bar' of null. The property foo is set by the reducer that processes SUCCESS.

If I add a delay to the SUCCESS effect, it all works nicely:

@Effect() success$ = this.actions$
    .ofType('SUCCESS')
    .delay(1)
    .map(() => ({type: 'ADVANCE'}));

But having to add this delay doesn't make sense to me.

I added console.log statements everywhere and the output looks like this:

  1. SUCCESS effect
  2. ADVANCE effect (showing route.foo === null)
  3. SUCCESS reducer (showing route.foo === something)
  4. Error

I expected the SUCCESS effect and the SUCCESS reducer to run before the ADVANCE effect.

Am I doing something wrong?

Is it incorrect to expect that actions are processed by the reducers in the same order that they are dispatched?


Versions:

  • @angular/cli: 1.0.0-beta.32.3
  • node: 7.5.0
  • os: darwin x64
  • @angular/common: 2.4.7
  • @angular/compiler: 2.4.7
  • @angular/core: 2.4.7
  • @angular/forms: 2.4.7
  • @angular/http: 2.4.7
  • @angular/platform-browser: 2.4.7
  • @angular/platform-browser-dynamic: 2.4.7
  • @angular/router: 3.4.7
  • @angular/cli: 1.0.0-beta.32.3
  • @angular/compiler-cli: 2.4.7
  • @ngrx/[email protected]
  • @ngrx/[email protected]
  • @ngrx/[email protected]
  • rxjs: 5.1.1
3
What versions are you using? With a similar arrangement, I see the reducer handle the action before the effect. - cartant
@cartant - I added the versions to the question. Thank you for checking. - Carlos Mermingas
My @ngrx versions are the same. What about your RxJS version? Mine is 5.2.0. You might also want to include your NgModule bootstrapping in the question. - cartant
The store (via the reducer manager) and the effect's actions observable both subscribe to the dispatcher. It sounds like your effects are somehow subscribing the dispatcher before the store. - cartant
@cartant - I have rxjs 5.1.1. I spent hours yesterday debugging this and didn't get anywhere. I'll spend some more today. - Carlos Mermingas

3 Answers

2
votes

To answer your concluding questions:

  • Am I doing something wrong?
  • No, I don't reckon you are doing anything wrong.
  • Is it incorrect to expect that actions are processed by the reducers in the same order that they are dispatched?
  • That order would seem logical but apparently you can't expect that at the moment

There seems to be a bug causing this behaviour. You are most probably experiencing it since you are directly mapping to another action. Usually, if you have an asynchronous operation or something like that, the reducer has time to finish before the effect listening to the next action starts.

Perhaps not a part of your question, but a solution to your specified problem would be to navigate to your new route directly in SUCCESS with the help of your payload, or pass the payload to ADVANCE.

Links below are reported issues in effects-, store- and rxjs-projects that are related to yours:

Seems like they're working on it :)

0
votes

It might work to replace your .map in the catch of the START action with a .concatMap and put both your map to SUCCESS and to ACTION there.

@Effect() start$ = this.actions$
    .ofType('START')
    .map(toPayload)
    .switchMap(input => doAsyncTask(input)
        .concatMap(result => {
            return Observable.from([type: 'SUCCESS', payload: result,
                                    type: 'ADVANCE'])
        .catch(error => ({type: 'ERROR', payload: error})));
-2
votes

The purpose of ngex/effects is to act as the middle-ware in a redux based environment.

enter image description here

So what you are basically doing here is calling the 'success' action that should work on both reducer and continue to another action is the middle-ware. But that's the consonant of effecs, It should intercept actions and handle them by itself, Only after is async handling it dispatch action.

So what is happening here? you dispatch success to two different places, one should handle by the reducer and one by the middle-ware. I'm not sure if it always like that but middle-wares will intercept incoming action before it hits the reducer. I think that if you'll modify you code a little bit it should work. Try to do something like :

@Effect() start$ = this.actions$
    .ofType('START')
    .map(toPayload)
    .switchMap(input => doAsyncTask(input)
        .map(result =>{
               this.store$.dispatch(
               {type: 'SUCCESS',
                payload: result
                });
               this.store$.dispatch(
               {type: 'ADVANCE'})

}

        .catch(error => ({type: 'ERROR', payload: error})));

You see what I'm doing here? I've removed the middle-ware that intercept 'success', So not the 'success' hits the reducer. Right after it it dispatch another action of type 'advance' that should hit your middle-ware after the 'success' is done been handling.

Hope it help's.