ngrx effect not being called when action is dispatched from component
Asked Answered
S

6

29

I am having an issue with the ngrx store not dispatching an action to the effect supposed to deal with it.

Here is the component that tries to dispatch:

  signin() {
    this.formStatus.submitted = true;
    if (this.formStatus.form.valid) {
      this.store.dispatch(new StandardSigninAction(this.formStatus.form.value.credentials));
    }
  }

The actions:

export const ActionTypes = {
  STANDARD_SIGNIN: type('[Session] Standard Signin'),
  LOAD_PERSONAL_INFO: type('[Session] Load Personal Info'),
  LOAD_USER_ACCOUNT: type('[Session] Load User Account'),
  RELOAD_PERSONAL_INFO: type('[Session] Reload Personal Info'),
  CLEAR_USER_ACCOUNT: type('[Session] Clear User Account')
};

export class StandardSigninAction implements Action {
  type = ActionTypes.STANDARD_SIGNIN;

  constructor(public payload: Credentials) {
  }
}
...

export type Actions
  = StandardSigninAction
  | LoadPersonalInfoAction
  | ClearUserAccountAction
  | ReloadPersonalInfoAction
  | LoadUserAccountAction;

The effect:

  @Effect()
  standardSignin$: Observable<Action> = this.actions$
    .ofType(session.ActionTypes.STANDARD_SIGNIN)
    .map((action: StandardSigninAction) => action.payload)
    .switchMap((credentials: Credentials) =>
      this.sessionSigninService.signin(credentials)
        .map(sessionToken => {
          return new LoadPersonalInfoAction(sessionToken);
        })
    );

I can see in debug that the component does call the dispatch method. I can also confirm that StandardSigninAction is indeed instantiated because the breakpoint in the constructor is hit.

But the standardSignin$ effect is not called...

What can possibly cause an effect not being called?

How can I debug what is going on within the store?

Can someone please help?

P.S. I do run the above effect as follows in my imports:

EffectsModule.run(SessionEffects),

edit: Here is my SessionSigninService.signin method (does return an Observable)

  signin(credentials: Credentials) {
    const headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'});
    const options = new RequestOptions({headers: headers});
    const body = 'username=' + credentials.username + '&password=' + credentials.password;
    return this.http.post(this.urls.AUTHENTICATION.SIGNIN, body, options).map(res => res.headers.get('x-auth-token'));
  }
Southeastward answered 22/3, 2017 at 20:53 Comment(5)
Are you using the most up-to-date versions of the @ngrx libraries? Do you have other effects that are working? Do you have other effects in SessionEffects that are working?Wakeless
Yes I do have other effects in SessionEffects that are workingSoutheastward
Can someone please suggest ways to debug the ngrx store, effects and actions?Southeastward
I have opened a separate question here: stackoverflow.com/questions/42985102Southeastward
is your store accessible like state => state.root.mystore or directly like state.root. Have you included the effects in app.module.ts EffectsModule.forRoot([MyFirstEffects, MySecondEffects]) these questions have got me before.Console
W
22

This is not going to be a definitive answer, but, hopefully, it will be helpful.

Before you start:

  • Make sure you are using the latest versions of the @ngrx packages (that are appropriate for the version of Angular you are using).
  • If you've updated any packages, make sure you re-start your development environment (that is, restart the bundler, the server, etc.)

If you've not done so already, you should have a look at the implementation of the Store - so that you make some educated guesses as to what could be going wrong. Note is that the Store is pretty light. It's both an observable (using the state as its source) and an observer (that defers to the dispatcher).

If you look at store.dispatch you'll see that it's an alias for store.next, which calls next on the Dispatcher.

So calling:

this.store.dispatch(new StandardSigninAction(this.formStatus.form.value.credentials));

should just see an action emitted from the dispatcher.

The Actions observable that's injected into your effects is also pretty light. It's just an observable that uses the Dispatcher as its source.

To look at the actions that are flowing through the effect, you could replace this:

@Effect()
standardSignin$: Observable<Action> = this.actions$
  .ofType(session.ActionTypes.STANDARD_SIGNIN)

with this:

@Effect()
standardSignin$: Observable<Action> = this.actions$
  .do((action) => console.log(`Received ${action.type}`))
  .filter((action) => action.type === session.ActionTypes.STANDARD_SIGNIN)

ofType is not an operator; it's a method, so to add do-based logging, it needs to be replaced with a filter.

With the logging in place, if you are receiving the action, there is something wrong with the effect's implementation (or maybe the action types' strings/constants aren't what you think they are and something is mismatched).

If the effect is not receiving the dispatched action, the most likely explanation would be that the store through which you are dispatching the StandardSigninAction is not that same store that your effect is using - that is, you have a DI problem.

If that is the case, you should look at what differs from the other SessionEffects that you say are working. (At least you have something working, which is a good place to start experimenting.) Are they dispatched from a different module? Is the module that dispatches StandardSigninAction a feature module?

What happens if you hack one of the working SessionEffects to replace its dispatched action with StandardSigninAction? Does the effect then run?

Note that the questions at the end of this answer aren't questions that I want answered; they are questions that you should be asking yourself and investigating.

Wakeless answered 24/3, 2017 at 2:17 Comment(2)
Hi and thank you very much cartant. I took into account the advice you kindly provided and I think I am onto a possible resolution of the issue... I will comment here when I have pinpointed and completely understood the cause of the issue.Southeastward
I'm struggling with similar issue - can you share your findings? I need to dispatch an action from featured module and catch it in another featured module effect.Wertz
T
16

Your store's stream may be stopping because of either unhandled errors or - perhaps more confusingly - errors that seem 'handled' using .catch that actually kill the stream without re-emitting a new Observable to keep things going.

For example, this will kill the stream:

this.actions$
    .ofType('FETCH')
    .map(a => a.payload)
    .switchMap(query => this.apiService.fetch$(query)
        .map(result => ({ type: 'SUCCESS', payload: result }))
        .catch(err => console.log(`oops: ${err}`))) // <- breaks stream!

But this will keep things alive:

this.actions$
    .ofType('FETCH')
    .map(a => a.payload)
    .switchMap(query => this.apiService.fetch$(query)
        .map(result => ({ type: 'SUCCESS', payload: result }))
        .catch(e => Observable.of({ type: 'FAIL', payload: e}))) // re-emit

This is true for any rxjs Observable btw, which is especially important to consider when broadcasting to multiple observers (like ngrx store does internally using an internal Subject).

Tay answered 16/8, 2017 at 20:53 Comment(1)
This is definitely the answer. If you look at vendor.bundle, you can put a breakpoint on ActionSubject.prototype.error and find the offending error.Bandy
Y
11

I am using a later version of ngrx (7.4.0), so cartant's suggestion of:

.do((action) => console.log(`Received ${action.type}`))

should be...

... = this.actions.pipe(
   tap((action) => console.log(`Received ${action.type}`)),
   ...

And in the end I discovered I had missed adding my new effects export to module, like:

EffectsModule.forRoot([AuthEffects, ViewEffects]),  // was missing the ', ViewEffects'
Yardage answered 6/7, 2019 at 15:27 Comment(1)
This answer helped me as I had missed adding the effects in EffectsModule.forRootPreconception
E
6

If you are using version 8, ensure you wrap each action with createEffect.

Example:

Create$ = createEffect(() => this.actions$.pipe(...))
Euratom answered 7/11, 2019 at 6:11 Comment(0)
L
0

Another possible reason is that if you used ng generate to create the module where you imported the Effects make sure it is imported in the App Module as the following command 'ng generate module myModule' will not add it to the app module.

Leonaleonanie answered 3/10, 2019 at 17:39 Comment(0)
B
0

For me I had everything working correctly. I removed my node_modules, npm install and it worked

Benbena answered 8/12, 2023 at 19:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.