[Too late now I'm sure, but I'll have a go at answering this. Note that although the plunkr still shows the code, it doesn't actually run - like most plunkr examples on SO these days]
Your provider setup has: provide: APP_INITIALIZER, useFactory: AppInitializerFactory, deps: ConfigServiceBase
and although it's not obvious from the function declaration (since you're not being explicit about the type of the return value), AppInitializerFactory
returns a function that returns a promise. This is important, since the Angular initialization process will wait for that promise to be resolved before continuing to load the rest of the app (loading the initial component, etc).
You also have provide: APP_BASE_HREF, useFactory: BaseHrefFactory, deps: ConfigServiceBase
. But, since this is not associated with APP_INITIALIZER
, Angular makes no particular guarantee as to when the BaseHrefFactory
will be executed (though it's unlikely to be executed until something asks for the APP_BASE_HREF
). The fact that it sometimes gets executed after AppInitializerFactory
may be pure luck.
To be clear, providers using the APP_INITIALIZER
token are guaranteed to be instantiated before the first component is created - but this is unrelated to the order in which services are instantiated. The dependency between services is expressed via the dependency-injection tree.
Note that the Angular router makes use of APP_INITIALIZER
itself - some of the initialization of the router happens there. So, it's possible that by adding routing into the mix you change the (already random) order in which things happen.
Another thing to bear in mind is that, although BaseHrefFactory
has a dependency on ConfigServiceBase
, it does not have a dependency on "an instance of ConfigService
whose load()
method has been called". The injector will quite happily pass it a reference to an instance of ConfigService
whose load()
method has not been called.
To solve all this, I would inject a service rather than try to inject a value, as follows.
1) Create a service to hold the value:
@Injectable()
export class AppBaseHrefService {
public baseHref: string;
}
2) Modify the AppInitializerFactory
function to take an AppBaseHrefService
parameter, and add an extra step to set the value based on the config:
export function AppInitializerFactory(config: ConfigServiceBase, appBaseHrefService: AppBaseHrefService) {
return () => config.load()
.then(() => {
appBaseHrefService.baseHref = config.baseHref
});
}
3) Give your APP_INITIALIZER
provider a dependency on AppBaseHrefService
:
{ provide: APP_INITIALIZER, useFactory: AppInitializerFactory, deps: [ConfigServiceBase, AppBaseHrefService], multi: true }
Of course, you may decide that it's easier to simply inject the existing ConfigService
/ ConfigServiceBase
rather than injecting yet another service. I'd be inclined to agree, unless your actual ConfigService
has lots of configuration information and you only want to expose some of it.
UPDATE July 2019, since no-one seems to be able to get this to work...
Firstly, please bear in mind that I'm not claiming to be able to set the value provided by APP_BASE_HREF
. Instead, I'm suggesting that you could inject something else that will provide the value you need.
I've created a StackBlitz that shows this in action. Here I have the following moving parts:
I have an InitializerProvider
provider defined in the AppModule
and associated with the APP_INITIALIZER
DI token. This uses a factory function to do asyncronous initialization. Because that function returns a Promise
, no components will be created etc. until that promise has been resolved (i.e. initialization is complete).
The factory function above uses the ConfigService
to asynchronously fetch "some data". It then uses that data to initialize the BaseUrlService
. More synchronous or asynchronous actions could be added here as necessary. (Async/await is great for this stuff).
Components (e.g. AppComponent
in this example) can inject BaseUrlService
and use that to access the base URL that's derived from the data fetched during initialization. Since the initialization happens before any components are created, there should be no circumstances in which those components see an un-initialized service.
HTH.