3
votes

Here is my app module:

@NgModule({
  declarations: [
    SampleA
  ],
  imports: [
    BrowserModule,
    EffectsModule.forRoot([APIEffects]),
    HttpClientModule,
    StoreModule.forRoot(reducers),
    StoreDevtoolsModule.instrument({
      maxAge: 25, // Retains last 25 states
      logOnly: environment.production, // Restrict extension to log-only mode
    }),
    KeyboardShortcutsModule.forRoot()
  ],
  providers: [
    APIService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpMockRequestInterceptor,
      multi: true
    }
  ],
  bootstrap: [],
  entryComponents: [
    SampleComponent
  ]
})
export class AppModule{...}

Here is APIEffect:

@Injectable()
export class APIEffects {
  fetchData$ = createEffect(() => this.actions$.pipe(
    ofType(Actions.apiFetchData),
    mergeMap((action) => this.apiService.fetchData(...action)
          .pipe(
            map(data => Actions.apiSuccess({ data })),
            catchError(() => of(Actions.apiCallFailed()))
          )
    )
  ));
}

Now if I disptach apiFetchData action in SampleComponent constructor, the effect doesn't get called:

export class SampleComponent {
    constructor(private store: Store<{ state: State }>) {
        store.dispatch(Actions.apiFetchData(...))
    }
}

If I dispatch the same action later in the lifecycle of the component, then everything works fine and the effect happens.

In Redux DevTools, I can see action dispatch happens before effects init. Here's the order:

@ngrx/store/init
[API] fetch data // this is the fetch data action
@ngrx/effects/init

So my question is how I can force effects to initialize before my component is constructed.

UPDATE

I haven't solved the issue mentioned above, but for now, I decided to use ROOT_EFFECTS_INIT, (thanks to @mitschmidt for mentioning it), and dispatch a series of actions after effects are ready to initialize the state of my app. Here's the effect that uses ROOT_EFFECTS_INIT:

  init$ = createEffect(() =>
    this.actions$.pipe(
      ofType(ROOT_EFFECTS_INIT),
      map(action => CoverageUIActions.apiFetchData({...}))
    )
  );
1

1 Answers

1
votes

In order to achieve that you might want to look at NGRX lifecycle events such as the ROOT_EFFECTS_INIT as described here: https://ngrx.io/guide/effects/lifecycle

Using the APP_INITIALIZER injection token (good tutorial here) plus the necessary syntactic sugar, you can construct an app initializer service that delays your app bootstrapping until NGRX root effects are registered (no component will be initialized in that case and your logic above inside the constructor should work). In a recent project I went down a similar road:

export class AppInitializer {
  constructor(private readonly actions$: Actions) {}

  initialize() {
    return this.actions$.pipe(ofType(ROOT_EFFECTS_INIT));
  }
}
  return (): Promise<any> => {
    return appInitializer.initialize().toPromise();
  };
}

... and finally add the provider to your module:

import { appInitializerFactory } from './app-initializer.factory';

export const APP_INITIALIZER_PROVIDER: Provider = {
  provide: APP_INITIALIZER,
  useFactory: appInitializerFactory,
  deps: [AppInitializer],
  multi: true,
};
// your module...
{
...
  providers: [APP_INITIALIZER_PROVIDER]
...
}