[X] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
EffectsFeatureModule and EffectsRootModule inject the effect in the module constructor. That means all the effects classes are instantiated during bootstrap of angular app.
When using ngUpgrade I bootstrap angular app first, and angularJS app second. My effect class depends on ngUpgraded service. Angular2+ attempts to create an instance of ngUpgraded service (and $injector) during angular bootstrap, before angularJS is bootstrapped.
This essentially prevents me from upgrading to NGRX v4 in the hybrid app.
EffectsModule doesn't inject the effects in the constructor.
@robwormald
Workaround I found that seems to work with ngUpgrade:
BEFORE:
@NgModule({
imports: [EffectsModule.forFeature([MyEffects])],
declarations: [MyComponent]
})
export class MyModule {}
AFTER:
@NgModule({
providers: [MyEffects]
})
export class MyModule {}
@Injectable()
export class MyEffects {
constructor(sources: EffectSources) {sources.addEffects(this);}
}
@Component()
export class MyComponent {
constructor(effects: MyEffects) {} // injected just so effects get registered
}
I am also coming across this issue and would love a solution that's built into the API. The workaround @ukrukarg proposed is difficult to implement if no Angular component is being used. Another workaround is to provide the Effect classes as a specific token that handles multiple instances. Then, in ngDoBootstrap following the bootstrap of the AngularJS side of things, the injector gets the token and has each class add itself to the effects sources.
For reference:
Special effects token:
export interface RegisterEffects { ngRegisterEffects(): void; }
export const EFFECTS_CLASS =
new InjectionToken<RegisterEffects[]>('EFFECTS_CLASS');
export function EffectsProvider<T extends RegisterEffects>(
effectsClass: Type<T>): ClassProvider {
return {
provide: EFFECTS_CLASS,
multi: true,
useClass: effectsClass,
};
}
ngRegisterEffects would look like this:
ngRegisterEffects() {
this.sources.addEffects(this);
}
A provider is added like so:
EffectsProvider(EffectsClass),
And the ngDoBootstrap becomes:
this.upgrade.bootstrap(appName);
const effectClasses = this.injector.get(EFFECTS_CLASS);
for (const effectClass of effectClasses) {
effectClass.ngRegisterEffects();
}
I believe the correct workaround is to simply add ngDoBootstrap to your root NgModule:
@NgModule({ ... })
export class RootModule {
ngDoBootstrap() { }
}
@brandonroberts and I maintain a very large hybrid app and everything works as expected with that added to the root module.
Do you have a repository where I can view this hybrid app @MikeRyanDev? I have ngDoBootstrap in my app and the issue still appeared.
@michaelgerakis the repository is not public. Can you provide a minimal reproduction of this issue using plunker or a github repo?
@ukrukarg @michaelgerakis Can this issue be closed?
@brandonroberts
I created a small public repro for this issue:
https://github.com/pobrienms/injectorRepro
See:
https://github.com/pobrienms/injectorRepro/commit/dba0dc613850b89a4271c118178a1a62b478130e
@brandonroberts I managed to update repro case by @pobrienms to reflect my setup better: https://github.com/ukrukarg/injectorRepro
Could this be reopened now?
Reopening. Will have a look
@ukrukarg Apologies for the inactivity on this issue. If this is still a problem with NgRx Effects v5 please open a fresh issue and we'll take a look!
I am having the same issue. Is there a workaround? I have same structure as @MikeRyanDev proposed I think*.
@NgModule({ ... })
export class RootModule {
ngDoBootstrap() { }
}
This is my ngBootstrap
@NgModule({
imports: [EffectsModule.forRoot(MyAwesomeEffects), AngularJSModuleProvider.forRoot()]
})
export class ApplicationShellModule {
constructor(
private upgrade: UpgradeModule,
private store: Store<fromRoot.State>
) {}
ngDoBootstrap(app: ApplicationRef){
this.upgrade.bootstrap(
document.querySelector('.spa-container'), [ajsModule.name], { strictDi: true });
app.bootstrap(AppComponent); // This bootstraps angular 8
this.store.dispatch(BootstrapActions.BootstrapSuccess()); // Sends bootstrap actions
}
}
This is my sample effect
import { Injectable, Inject } from '@angular/core';
import { Actions } from '@ngrx/effects';
@Injectable()
export class MyAwesomeEffects {
constructor(
@Inject('AngularJSService') private angularJSService: any,
private actions$: Actions
) {}
}
As soon as I inject @Inject('AngularJSService') private angularJSService: any, I get

I am on v8 of ngrx
"@ngrx/effects": "^8.1.0",
"@ngrx/router-store": "^8.1.0",
"@ngrx/store": "^8.1.0",
"@ngrx/store-devtools": "^8.1.0",
The solution that I generally use to workaround this is to lazy initialize the Injectable that I need. This only works if the effect that you're using the Injectable runs after AngularJS bootstraps, but for the most part this is working well enough for my purposes.
// Any lazy implementation will do, you can inline this if desired
// this one is adapted from https://www.dustinhorne.com/post/2016/05/09/factory-pattern-and-lazy-initialization-with-angularjs-and-typescript
export class Lazy<T> {
private instance: T = null;
private initializer: () => T;
constructor(initializer: () => T) {
this.initializer = initializer;
}
public get value(): T {
if (this.instance == null) {
this.instance = this.initializer();
}
return this.instance;
}
}
import { Injectable, Injector } from '@angular/core';
import { Actions } from '@ngrx/effects';
@Injectable()
export class MyAwesomeEffects {
constructor(
private readonly injector: Injector,
private actions$: Actions
) {
private _angularJSService = new jsCommon.Lazy<IAngularJsService>(() => this.injector.get('AngularJSService'));
private get angularJSService() { return this._angularJSService.value(); }
myEffect$ = createEffect(() =>
this.actions$.pipe(
ofType(myActions.Type),
mergeMap(action =>
return this.angularJSService.someMethod(); // <== injection happens here
)
)
);
}
}
In this example, the AngularJS injector is only used when the block inside mergeMap runs. Effects classes are constructed pretty early in the lifetime of an Angular application. Depending on how you have your app configured, this can happen before you call upgrade.boostrap(). By deferring the injection of your Angular JS service until the effect actually runs you give Angular JS time to bootstrap.
Of course, there's still potential that your effect runs before upgrade.boostrap() is called and lazy initialization won't save you. In these cases, you have some options:
Most helpful comment
I am also coming across this issue and would love a solution that's built into the API. The workaround @ukrukarg proposed is difficult to implement if no Angular component is being used. Another workaround is to provide the Effect classes as a specific token that handles multiple instances. Then, in ngDoBootstrap following the bootstrap of the AngularJS side of things, the injector gets the token and has each class add itself to the effects sources.
For reference:
Special effects token:
ngRegisterEffects would look like this:
A provider is added like so:
And the ngDoBootstrap becomes: