[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report
[ ] Feature request
[ ] Documentation issue or request
Using APP_INITIALIZER together with ngstore and effects does not work as expected. Effects are initialized before the promise via APP_INITIALIZER is completed. APP_INITIALIZER in this case is used to provide a at-runtime environment config via a get call to a JSON file.
Using code provided by @brandonroberts in #174 we can now semi-control when effects are run, except they are delayed by too much now, as the reducers are triggered before effects are loaded, and thus effects don't get triggered by said reducers.
More info and example code can be found in #174 which is now a Closed issue, however, problems remain, hence this ticket to more accurately address the specific issue.
None of the components of the reactive store are initialized until APP_INITIALIZER's Promise code has successfully completed.
Initial proposed fix code, which still shows the above issues can be seen at: https://github.com/ngrx/platform/issues/174#issuecomment-373853594
_Update:_ @paulwinner1995 provided the following repo with the issue reproduced:
https://github.com/paulwinner1995/effects-issue-example
Application fails on runtime, but if you comment out import of Effect module it works as expected.
I spent most of today trying to work out why my services were being instantiated before my APP_INITIALIZER had a chance to load their configuration from the server.
In the end I found that the the effects where being instantiated _before_ the APP_INITIALIZER promise had resolved.
This was causing the services to be created and injected with an empty configuration.
It would be more intuitive if everything (including effects) waited for the APP_INITIALIZERs to resolve - otherwise how do you initialize an app?
yep, the effects no doubt use some of your regular services, which in turn triggers them to be initialized. Also note that variable declarations at the start of your classes will run too, so best to declare your variables empty, and then use a function called from your constructor (or elsewhere) to actually fill them with your data after APP_INITIALIZER completes. also be careful with get declarations in your class, because if anything calls those, they will also trigger, and thus mess up if your variables rely on data produced during the APP_INITIALIZER stage (i.e. run-time loaded config)
Just put console.log or console.warn (adss stack traces) into loads of random constructors on services and components, and various functions in your store, and you'll soon see in the console if the order of things loaded is not as you were expecting.
P.s. hate that Close and comment button placement!
Yes - that's what I spent the day doing: adding console.log() calls to all of my constructors.
I'm amazed that is it so difficult to do something as simple as loading a config file before your application starts.
I'm taking a little break from this issue and implementing the run-time config in one of our other apps that does not use the store (This should be easier....??). My colleague however is continuing to work on this and I'll join him once our other app has been converted without using the store. Lets see if we can figure this out.
Hopefully some others join the quest here...
To summarize:
Angular provides the APP_INITIALIZER token to allow asynchronous tasks to be completed before the application starts.
There are numerous blogs and stackoverflow answers recommending that APP_INITIALIZER can be used to asynchronously load configuration data and add it to an injected ConfigService.
e.g. https://hackernoon.com/hook-into-angular-initialization-process-add41a6b7e
EffectModule.forRoot() causes the effects to be injected before the APP_INITIALIZER providers have run/resolved. Resulting in application services that are injected into effects classes being injected with an un-initialized ConfigService.
If the constructors of the services try to use values from the ConfigService the app will error.
Even if the Effects used the APP_INITIALIZER , we still can't guarantee that the asynchronous config would be read in time.
The only way I can see of being able to guarantee that an Angular app could be configured asynchronously would be for Angular to offer another unambiguous token e.g. APP_CONFIG, which would operate the same as APP_INITIALIZER, but block APP_INITIALIZER until all of the APP_CONFIG promises had resolved. But I'd be very happy if someone with more knowledge of Angular could tell me that I'm wrong.
In the meantime the only pragmatic approach I can think of is to not access, or cause to be accessed, the ConfigService in the constructors of your services. That would allow your APP_INITIALIZER a chance to populate the config information in your ConfigService before your app boostrapped.
So far this has only been a problem for me when using Effects - so having EffectsModule.runAfterBootstrap() back would be handy too.
Also: Thanks for ngrx - it is excellent.
+1
Just ran up against the same issues you're describing here which using OnRunEffects fails to solve. As @tmc101 points out, the minute you have an effect that requires an injected service, that requires a bootstrapped configuration you're out of luck.
My use case I'm trying to utilise swagger-codegen'd api modules which severely limits my ability to get creative as I need to treat this as blackbox. These require an injected configuration setting the URL basePaths of the APIs which I need to configure before the services are instantiated. By injecting these api services into my effects (which is my primary use-case for using effects in the first place) I end up like others with services with uninitialised api configuration as they are loaded before APP_INITIALIZER resolves.
+1
Faced with same issue that @chrismellard has described. I have Configuration service that fetch base url to server during bootstrapping. So other services depend on this configuration. When I use such services with effects my application crashes, because url was not loaded before effect initialized.
Will someone add a concrete reproduction of this issue? If using neither token works correctly, this would help us get the issue resolved or provide a workaround.
Hi @brandonroberts ,
I have created repository with reproduction of this issue https://github.com/paulwinner1995/effects-issue-example
Application fails on runtime, but if you comment out import of Effect module it works as expected.
I think the problem might be that you're accessing the config from in the ctor.
If you write your code like below, it does work.
getPosts(): Observable<Post[]> {
return this.httpClient.get<Post[]>(
`${this.configurationService.get("apiBaseUrl")}/posts`
);
}
I've also created a fork of your example that uses the bootstrapEffects method mentioned in an earlier post that works without errors.
@brandonroberts This fixed the issue for me. I think my previous endeavours at using this proposed method must have been flawed in some way.
Thanks a lot @brandonroberts , it helps me.
Ah, maybe we shall have a go at that.
Our current solution if of interest to all, was to use a Subject Observable in addition to the APP_INITIALIZER.
And thus the app would do its bootloader init stuff as expected using APP_INITIALIZER, and for the ngStore code, when the config service was inevitable loaded before the environment stuff was ready, it would hit the config service constructor, which then uses the Obserable, and sits there waiting, and be forced to wait until the Observeable is given the OK by the env config service successfully getting the data back, originally triggered by APP INIT.
Btw, @brandonroberts, re: provideBootstrapEffects() in the ngModule, if you do a production build / use AOT compiler with angular-cli, it complains that you should not have functions in the providers section of ngModule.
Hi @russelltrafford, Looks like in my case AOT compiler does't complain to function invocation on provider section. But I run production build with ng build -prod -sm command. Do you have any idea? I use angular 5.2.x and cli 1.7.3
I've just come across your originally articulated problem @russelltrafford around the effects not bootstrapping early enough for any actions fired in the AppComponent initialisation. I've modified the above repo from @brandonroberts to demonstrate this. An action fired in ngOnInit does not trigger the effect but the button click on the same component when clicked later on does of course trigger the effect. repo here - https://github.com/DatacomNelson/effects-issue-example
This provideBootstrapEffects() was working great but we do have need to trigger actions/effects during our AppComponent initialisation. @russelltrafford Can you elaborate more on your workaround please or fork the repo and add to it?
Ran into the same issue today. The above solution by @brandonroberts did work.
My Effects are in a feature module. So, had to pass an empty array to EffectsModule.forFeature and use provideBootstrapEffects instead:
@NgModule({
declarations: [],
imports: [
CommonModule,
StoreModule.forFeature('foo', FooReducer),
EffectsModule.forFeature([])
],
providers: [provideBootstrapEffects([FooEffects])]
})
But this is not needed for lazy loaded modules
Looks like I was wrong. This doesn't seem to work in feature modules, when using EffectsModule.forFeature. The effect is not even triggered.
Ok. It works on the first feature module, but not in others. I guess thats because providers are added to the same root injector and the last module with provideBootstrapEffects is overriding all others.
@jaichandra it could be tweaked some to add multi: true to the BOOTSTRAP_EFFECTS token, but then you have to flatten out the arrays of effects before registering them
Closing as resolved with an example workaround
@brandonroberts tried to run https://github.com/brandonroberts/effects-issue-example
Angular CLI: 6.1.5.
Effects are not getting invoked!
why a workaround, and not solve the problem ?
like it used to work in previous versions of NGRX ?
I don't think the workaround works. My effects are not getting called when I try dispatching an action..
We had to add a ready property to our config service that we are injecting. It feels gross and I look forward to ngrx honoring the app initializer.
Is there any ETA on this? Or any appetite to fix it (since there is a workaround)?
so this still not resolved? great.
Wow this feels like years later and still not addressed? I've moved into a
new project with backbone.js now lol
On Mon., Feb. 11, 2019, 14:41 Amel Salibašić <[email protected]
wrote:
so this still not resolved? great.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/ngrx/platform/issues/931#issuecomment-462524076, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AVRQYq5vdoDmCfE13HyB-gmo3XIWzqhtks5vMfF9gaJpZM4S5D3h
.
@russelltrafford can you provide some details of your solution with Subject + APP_INITIALIZER?
Anyone found some alternative way to have an "all case working" solution?
A tried also to move all the effects and their dependecies in a module loaded lazily on the promise callback.
It works but anyway it forces the developer to keep in mind this issue and makes the app harder to configure.
Maybe there is a way to load some server side stuff before angular itself and then let angular load it synchronously?
Sorry it was a long time ago and Im no longer at that company and project,
so don't have access to the code anymore. I gave a rough explanation at the
time, if there's specifics that I wrote that you have confusion about I'll
try and help if I remember.
On Thu., Feb. 14, 2019, 09:20 Giulio Ruggeri <[email protected]
wrote:
@russelltrafford https://github.com/russelltrafford can you provide
some details of your solution with Subject + APP_INITIALIZER?
Anyone found some alternative way to have an "all case working" solution?
A tried also to move all the effects and their dependecies in a module
loaded lazily on the promise callback.
It works but anyway it forces the developer to keep in mind this issue and
makes the app harder to configure.
Maybe there is a way to load some server side stuff before angular itself
and then let angular load it synchronously?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/ngrx/platform/issues/931#issuecomment-463713252, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AVRQYoxFe5CDYH8uFtr-rQg_6dKaSYXEks5vNZrlgaJpZM4S5D3h
.
Our workaround is to delay load of the application until the configuration is loaded within main.ts. We do this to toggle Rest API urls and OAuth client id settings between different deployed environments.
replace this:
platformBrowserDynamic().bootstrapModule(AppModule)
with this:
AppConfigService.loadConfig().then(() => {
return platformBrowserDynamic().bootstrapModule(AppModule);
})
This means the code loading the configuration cannot rely on Angular's HttpClient.
import { Injectable } from '@angular/core';
import { AppConfig } from './app-config.models';
@Injectable({ providedIn: 'root' })
export class AppConfigService<T extends AppConfig = AppConfig> {
static appConfig: AppConfig;
constructor() { }
static loadConfig(): Promise<void> {
return new Promise((resolve, reject) => {
const oReq = new XMLHttpRequest();
oReq.addEventListener('load', (resp) => {
if (oReq.status === 200) {
try {
AppConfigService.appConfig = JSON.parse(oReq.responseText);
} catch (e) {
reject(e);
}
resolve();
} else {
reject(oReq.statusText);
}
});
oReq.open('GET', 'clientsettings.json');
oReq.send();
});
}
getConfig(): T {
return AppConfigService.appConfig as T;
}
}
A sample config:
export interface AppConfig {
production: boolean;
}
export interface XyzAppConfig extends AppConfig {
abcApi: string;
abcClientId: string;
restApiBase: string;
foo: Bar;
}
You can still inject this config service into your other services that make HttpClient calls and use the config settings to set the correct url paths for the HttpClient requests.
constructor(private http: HttpClient,
private config: AppConfigService<XyzAppConfig>) {
}
There is no need of usage of APP_INITIALIZER here. Just use redux to load config.json in app.component.ts. You dispatch action on init, and then effect should take care of loading config.json with httpclient, and dispatch another action which then stores config into state.
this.store.dispatch(new LoadConfig());
@Effect()
private loadConfig$: any = this.actions$.pipe(
ofType(AppActionTypes.LoadConfig),
mergeMap(() =>
this.httpClient.get('assets/appsettings.json').pipe(
map((response: AppConfig) => ({
type: AppActionTypes.LoadConfigSuccess,
payload: response
})),
catchError(() => of({ type: AppActionTypes.LoadConfigError }))
)
)
);
that's it. you take care about payload assignation in reducer.
@nukec If you have multiple Effects that all depend on service configuration, this can get really tedious to copy this code around. It really is better to have the configuration loaded earlier.
Thanks to @brandonroberts for his workaround. However, when provideBootstrapEffects was called multiple times, effects ended up shadowed and not all of them where injected.
Here is what we came up with:
import {APP_BOOTSTRAP_LISTENER, Type} from '@angular/core';
import {EffectSources} from '@ngrx/effects';
/**
* See https://github.com/ngrx/platform/issues/931
* This utility class is based on the workaround suggested by Brandon Roberts here https://github.com/brandonroberts/effects-issue-example
* We had to refactor it and use dynamically generated injection tokens for the effects injection to avoid their shadowing when
* EffectsBootstrapProvider.provide is called multiple times.
*/
export abstract class EffectsBootstrapProvider {
static provide (effects: Type<any>[]) {
const EFFECTS_BOOTSTRAP = `__effects_bootstrap_${Math.random().toString(36)}`;
return [
effects,
{ provide: EFFECTS_BOOTSTRAP, deps: effects, useFactory: (...instances: any[]) => instances },
{
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useFactory: (_effects: Type<any>[], sources: EffectSources) => () => {
_effects.forEach(effect => sources.addEffects(effect));
},
deps: [ EFFECTS_BOOTSTRAP, EffectSources ]
}
];
}
}
EDIT
Even better:
import {APP_BOOTSTRAP_LISTENER, Type} from '@angular/core';
import {EffectSources} from '@ngrx/effects';
/**
* See https://github.com/ngrx/platform/issues/931
* This utility class is based on the workaround suggested by Brandon Roberts here https://github.com/brandonroberts/effects-issue-example
* We had to refactor it and use the effects type as injection token for the effects injection to avoid their shadowing when
* EffectsBootstrapProvider.provide is called multiple times.
*/
export abstract class EffectsBootstrapProvider {
static provide (effects: Type<any>[]) {
return [
effects,
{
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useFactory: (sources: EffectSources, ..._effects: Type<any>[]) => () => _effects.forEach(effect => sources.addEffects(effect)),
deps: [ EffectSources, ...effects ]
}
];
}
}
As an Angular novice I would appreciate some feedback on this implementation. Thanks for reading!
I've also created a fork of your example that uses the
bootstrapEffectsmethod mentioned in an earlier post that works without errors.
I’ve faced an issue with this workaround.
Based on docs all callbacks provided via APP_BOOTSTRAP_LISTENER will be called for every component that is bootstrapped. Hence root effects are registered after AppComponent initialized, so when I dispatch an action inside ngOnInit there is no effect yet.
Is there any way to register root effects after APP_INITIALIZER has been resolved and before AppComponent bootstrapped? 🤔
I implemented ngrxOnInitEffects for effects, listen for init action and map to the action I was originally dispatching inside of ngOnInit of my AppComponent, but not sure it's a good idea.
I ended up loading config with root effects before feature effects are loaded.
The provideBootsrapEffects works fine for root effect but when used in a feature module the ngrxOnInitEffects is never called for that effect. Is there any plan to have an actual solution built in for this?
@brandonroberts why is it closed? I think, ngrx is breaking "angular rules" :-(
ngrx-data also breaks the rules, and runs before aPP_INITIALIZER finishes !
please re-open
This is a major issue for us, which I think should get resolved within effects and not via a workaround. We're using Azure deployments, with specific app settings based on environment that need to get loaded to pull api strings before any service calls are made in the effects.
@brandonroberts workaround worked for me => https://github.com/brandonroberts/effects-issue-example/blob/master/src/app/app.module.ts
It also works with AOT.
Thanks a lot
This is still a problem in 8.6.0 version. Please reopen this issue.
@brandonroberts - can you please help us by fixing this?
Is there any fix for this?
Angular CLI: 10.0.5
Angular: 10.0.8
Any updates on this issue?
Thanks @michael-lang for the workaround! Worked nicely in my app.
Thank you @michael-lang for the suggestion. We are trying it out in our app now with a slight modification, we made it async and also had it provide the production flag as to help us support not determining environments from builds.
AppConfigService.loadConfig().then(async () => {
if (AppConfigService.isProduction) {
enableProdMode();
}
try {
return platformBrowserDynamic().bootstrapModule(AppModule);
} catch (err) {
return console.error(err);
}
});
Most helpful comment
I've also created a fork of your example that uses the
bootstrapEffectsmethod mentioned in an earlier post that works without errors.https://github.com/brandonroberts/effects-issue-example