Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@ngrx/store/init firing before effects are listening #103

Closed
phillipzada opened this issue Jul 19, 2017 · 26 comments
Closed

@ngrx/store/init firing before effects are listening #103

phillipzada opened this issue Jul 19, 2017 · 26 comments

Comments

@phillipzada
Copy link
Contributor

Since the upgrade, INIT actions are no longer being pickup up via any effects.

I've also confirm that I'mm using the EffectsModule.forRoot([AppEffects])

Test Setup
image

@MikeRyanDev
Copy link
Member

Ah, sorry! This is intentional but we forgot to mention it in the migration guide. You can refactor that action to look like this for the same behavior:

import { defer } from 'rxjs/observable/defer';

@Effect()
init$: Observable<Action> = defer(() => {
    console.log('ok');
    const username = '';
    const password = '';

    return of(new auth.LoginAction({ username, password });
  });

Will gladly accept a PR that adds a more thorough example to the migration guide!

@zygimantas
Copy link

zygimantas commented Jul 19, 2017

Strange, if I use

@Effect() public readonly initEffect = Observable.defer(() => Observable.of(new Action1()));

then neither Action1, nor any other effect is executed. And if I remove initEffect, other effects works as expected. Probably I haven't migrated properly.

@gmickel
Copy link

gmickel commented Jul 19, 2017

I have the same issue as @zygimantas

My project has a Init effect that uses defer(). After upgrading to ngrx v4 all actions after the Init effect are no longer run/working.

@zygimantas
Copy link

@brandonroberts @MikeRyanDev is that a bug and we should reopen this issue or "defer" is not the best way to do it?

@maxisam
Copy link
Contributor

maxisam commented Jul 20, 2017

UPDATE

https://ngrx.io/guide/effects/lifecycle

Original Comment

If I use defer and return another Observable directly, it doesn't work.

    @Effect() init$ = Observable.defer(() => {
        return Observable.of({ type: RootActions.INIT });
    });

    @Effect() applicationInit$ = this._Actions$
        .ofType(RootActions.INIT)
        .switchMap((action: any) => this.initial());

So I double check how defer really works. http://reactivex.io/documentation/operators/defer.html

The Defer operator waits until an observer subscribes to it, and then it generates an Observable

I guess defer generates the Observable before next effect observer.

So if I change the sequence, everything works. (Just move init$ to the bottom of the file.)

I think defer is really just a hack.

@zygimantas
Copy link

zygimantas commented Jul 20, 2017

@maxisam I can confirm, that after moving the init$ with defer to the bottom, applicationInit$ is executed, BUT other actions, dispatched from component, using this.store.dispatch(new Action3()); are not handled by effects.

@maxisam
Copy link
Contributor

maxisam commented Jul 20, 2017

@zygimantas I don't have the problem you have.

@maxisam
Copy link
Contributor

maxisam commented Jul 20, 2017

I think another workaround is dispatching a init action in app root module instead of using defer.

@zygimantas
Copy link

zygimantas commented Jul 20, 2017

Nice tip @maxisam ! Solved my problem with effects not executing after init+defer. This should be in the migration guide.

@southeastcon2017
Copy link

@maxisam can you give a quick sentence on how you do that? :)

@Kaffiend
Copy link

Kaffiend commented Jul 22, 2017

Does startWith not work? example here

@Kaffiend
Copy link

Kaffiend commented Jul 22, 2017

From what i gather we are trying to accomplish the same thing, on app startup perform something. In my electron app i use the startWith in my init effect to read in the json config file.

    // Reads and initializes the client from local config file if exists.
    @Effect() public init$: Observable<fromClientConfig.All>
    = this.actions$.ofType(fromClientConfig.LOAD)
        .startWith(new fromClientConfig.LoadAction())
        .switchMap(() => this.configService.readConf())
        .map((conf) => new fromClientConfig.InitSuccessAction(conf))
        .catch(err => of(new fromClientConfig.InitFailedAction()));

this then fires off this effect if it fails

 @Effect() public initFailed$: Observable<fromClientConfig.All>
    = this.actions$.ofType(fromClientConfig.INIT_FAILED)
        .switchMap(() => this.configService.writeConf(this.configService.defaults))
        .map((conf) => new fromClientConfig.InitSuccessAction(conf))
        .catch(err => of(new fromClientConfig.ConfigSaveFailedAction()));

@maxisam
Copy link
Contributor

maxisam commented Jul 22, 2017

@southeastcon2017 dispatching a init action in your app root module? You just need to inject store into your app root component and dispatch an action for your effect to work.

@zygimantas
Copy link

Or you can do it in onNgInit in app.component.ts, like you do in any other component, if that feels more natural than writing code directly in the module class.

@dfmartin
Copy link

dfmartin commented Aug 6, 2017

One thing that appears to work for me right now is to inject the store into my effects class. Then use it from within the defer to dispatch the event I need.

@Injectable()
export class AppEffects {
  @Effect({dispatch: false} appInit$: Observable<any> = defer(() => {
    console.log('appInit effect')
    this.store.dispatch({ type: 'application_start', payload: { /* some app data */ })
  })

  // some more effects

  constructor(private actions$: Actions, private store: Store<any> {}
}

@rpm911
Copy link

rpm911 commented Aug 8, 2017

When I added defer to my effects class, the other effects stopped working. I believe this is the same issue that @zygimantas had. I tried moving the defer to the bottom of my effects class as suggested by @maxisam and that did not fix it. Next I tried moving the defer to a separate effects class. That also did not work until I made the new effects class the last one in the EffectsModule.forRoot() call.

UPDATE
I got it to work by combining the @maxisam suggestion to put the init at the end plus the @dfmartin technique of injecting the store into the effects class to dispatch the initialization action. This allowed me to eliminate the separate effects class that was just used for the defer initialization. Seems that there must be a better way...

Guys, this seems awfully fragile and as @maxisam said, this seems like a hack.

@michaelwaihenya
Copy link

michaelwaihenya commented Nov 27, 2017

What works for me is creating an action derived class returning type INIT and using startWith. This is consistent with my other action classes.

import { INIT } from '@ngrx/store';
export class StoreInit implements Action {
get type() {
return INIT
}
}

and then

@Effect()
initSession$: Observable<Action> = this.actions$
    .ofType(INIT)
    .startWith(new StoreInit())
    .switchMap(action => this.authService.afAuth.authState)
    .mergeMap(auth => [new SessionStartSuccess(auth ? auth.uid : null)])

@bbaki
Copy link

bbaki commented Dec 19, 2017

For us changing from @ngrx/store/init to @ngrx/effects/init worked.

@navrudh
Copy link

navrudh commented Jan 7, 2018

import { ROOT_EFFECTS_INIT } from '@ngrx/effects' works

@michelcve
Copy link

I agree with @rpm911 that the current solution is fragile at best. @MikeRyanDev is there a better solution than using defer()?

@simeyla
Copy link

simeyla commented Oct 1, 2018

Wow. This is not the kind of thread you want to read when getting started on @ngrx.
Is there an official doc what we're supposed to actually do?

@juanmendes
Copy link

This was closed without a real solution? A bunch of hacks that only work in some cases? Effects/reducers on my app aren't being triggered at all. It'd be great if one of them maintainers did a write up of these common problems with a definitive explanation of these cases, now I have to try all these different approaches and hope one works for me. I'll blame it on myself for only upgrading ngrx > 4 in Nov 2018.

@jonrimmer
Copy link
Contributor

The problem with defer() is that while it delays creation of the observable until it is subscribed, the created observable will still push its value immediately. This will happen while NgRx is still in the process of subscribing to the defined effects, and before later effects have been subscribed to. This is why people are getting different results depending on the order they have defined their effects.

What we really need to do is delay the push of the action instead. The way to do this using RxJS is with a scheduler. Therefore, I would suggest not using defer() and instead using the RxJS asyncScheduler to delay dispatch of the action to a later event loop task, so NgRx has a chance to finish initializing all the effects before the value is pushed:

import { asyncScheduler, of } from 'rxjs';

@Effect()
$init = of(new LoginAction({ username, password }), asyncScheduler);

@SebasG22
Copy link

@jonrimmer Thanks, works as expected.

@dmitriydementor
Copy link

@jonrimmer , I have the latest version of NgRx, but when I use

@Effect()
$init = of(new notificationActions.loadNotifications(), asyncScheduler)

I get compile error Only a void function can be called with the 'new' keyword.

My action doesn't have payload:

export const loadNotifications = createAction(
    NotificationActionTypes.LoadNotifications,
)

Maybe I'm missing something?

@brandonroberts
Copy link
Member

@dmitriydementor you don't use new with an action creator. Just call the function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests