Store: ngxsOnInit runs before APP_INITIALIZER

Created on 16 Aug 2018  路  18Comments  路  Source: ngxs/store

Versions

* ngxs: 3.2.0
* @angular/core: 6.1.1

Repro steps

Stackblitz / Github link: https://stackblitz.com/edit/ngxs-simple-xjaodj

Run the repro, read the output and then read the code in the app directory to understand the output.

Observed behavior

ngxsOnInit runs before APP_INITIALIZER gets a chance to initialize app services.

Hello from AppState::ngxsOnInit!
Hello from a simple APP_INITIALIZER!
Hello from an asynchronous APP_INITIALIZER!
Hello from AppComponent::ngOnInit!

Desired behavior


ngxsOnInit should somehow wait for all provided APP_INITIALIZER functions to complete.

Hello from a simple APP_INITIALIZER!
Hello from an asynchronous APP_INITIALIZER!
Hello from AppState::ngxsOnInit!
Hello from AppComponent::ngOnInit!

Mention any other details that might be useful (optional)

I think the correct way to ensure that all APP_INITIALIZER functions have completed is to wait for the first NavigationStart event.

Or even better: provide your own APP_INITIALIZER that runs after every user-defined APP_INITIALIZER.

ready to release

Most helpful comment

Something new about this issue?

All 18 comments

I think that's why some of my services are not available in custom plugin. Could it be?

Something new about this issue?

@markwhitfeld whether it is breaking changes if will be fixed?

Any fix for this is breaking in the sense that it is a change in observable behavior, which some people may depend on.

I'd love to know the view on fixing this. I have a dynamic app config plugin I wrote that relies on APP_INITIALIZER to download some config data for base URLs before the API services are called. We just started implementing NGXS and our app is blowing up because it can no longer fetch that config data in advance of the store loading up.

In order for this to not be a breaking change we could create another lifecycle hook ngxsAfterInit which gets fired at the correct time.
Would anyone like to attempt a PR for this?

Cc @deebloo @splincode @eranshmil
Are we happy to have another lifecycle hook?

Yeah, that's fine with me.

I think I need to take into integrate with HMR hooks. Maybe ngxsAfterOnInit better give another name ngxsStateChecked.

Lifecycle + HMR integretion

ngxsOnInit -> ngxsHmrAfterOnInit
ngxsStateChecked -> ngxsHmrAfrterStateChecked

@DanaEpp as a solution to your problem why don't you dispatch a ConfigLoaded action when your config has loaded and then respond to this in your State classes.

Today we can use ngxsAfterBootstrap, this will be released in 3.4.0

Thanks @splincode, works great in latest dev build.
https://stackblitz.com/edit/ngxs-simple-rqsxyy

I'm on 3.5.1 and it seems that things are still initialized before APP_INITIALIZER.
Not sure if I'm missing something or if I don't completely understand what this issue's resolution should be?

This is my use case:
I need to initialize Ngxs through a library module that either is imported or not in the apps (monorepo structure)
And I couldn鈥檛 find a way to initialize it properly (the apps that import it should pass further if production is true or not, since a library shouldn't care about the app's environment directly)
But it seems that the initialization still takes place before APP_INITIALIZER resolves
This is my stackblitz: https://stackblitz.com/edit/ngxs-app-initializer?file=src%2Fapp%2Fstore%2Fstore.module.ts
The console will show:

prev state
next state
APP_INITIALIZER
APP_INITIALIZER Resolved
NGXS Logger shouldn't log anymore (but it does)
payload
prev state
next state
Angular is running in the development mode. Call enableProdMode() to enable the production mode.

but it should have production: true (I pass that hardcoded, since I don't have environments in the stackblitz) and developmentMode should be false. Also, the logger shouldn鈥檛 log.

P.S. Because of hmr, you need to reload the right-side browser window every time you make a change (at first I was tempted to think that the problem was resolved because of HMR, I wouldn't get the console logs all the time... but pressing reload shows every time that ngxs initializes before APP_INITIALIZER resolves)

Thanks @splincode !

Unfortunately, my issue is not inside the state itself...

It's that this line: NgxsModule.forRoot([], { developmentMode: <true or false> }) (and also the plugins initialization) inside the imports of the @NgModule decorator of MyStoreModule is happening before APP_INITIALIZER runs/resolves. And I don't have my boolean value (developmentMode: ) before APPP_INITIALIZER resolves.
The StackBlitz illustrates better what I'm trying to achieve.
I still want to believe that it must be possible to pass async values to initialization logic that happens inside @NgModule decorator. As far as I read, that's what APP_INITIALIZER is for...

Basically, in the provided stackblitz there shouldn't have been any logs from the logger plugin, because in my AppModule is do MyStoreModule.forRoot({ production: true }) which should get to MyStoreModule and to StoreService, which is then used like NgxsLoggerPluginModule.forRoot({ disabled: StoreService.production }) in MyStoreModule's @NgModule decorator

In production you don't need use LoggerPlugin and DevtoolsPlugin
https://www.ngxs.io/v/master/recipes/dynamic-plugins

You can register plugins at compile time

Basically, that's what I'm trying to achieve using disabled: true/false: NgxsLoggerPluginModule.forRoot({ disabled: <boolean value> }).
But this initialization happens inside a library module. And libs aren't/shouldn't be aware of the environment of the app that consumes the library, so I need to somehow tell it what's the right boolean value when I import it in an app:

import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

import { MyStoreModule } from '@my-namespace/store';
import { environment } from '../environments/environment';

@NgModule({
  imports: [
    // ...
    MyStoreModule.forRoot({ production: environment.production })
  ],
  // ...
})
export class AppModule {}

And then in my library's MyStoreModule module I need to somehow get that "production" value that's passed from the app, in the forRoot, and use it inside @NgModule on NgxsLoggerPluginModule.forRoot({ disabled: <that boolean value> })

Anyway, it's a Sunday, you've already spent too much time on Github on my behalf :D sorry about that! I didn't think I'd get a reply today.

EDIT: I was trying to apply the solution presented here for firebase init: https://github.com/nrwl/nx/issues/208#issuecomment-536169371
But can't seem to make it work

you can write your suggestions here
https://github.com/ngxs/store/issues/1417

Was this page helpful?
0 / 5 - 0 ratings