platform
platform copied to clipboard
No Provider issue on lazy loaded module and ViewContainerRef with Angular 14
Which @ngrx/* package(s) are the source of the bug?
router-store
Minimal reproduction of the bug/regression with instructions
Since we bumped versions from Angular V13 to V14, and ngrx 13.0 to 14.2, we have an error :
NullInjectorError: NullInjectorError: No provider for Store!
We need to declare our Store in a lazy loaded module :
StoreModule.forRoot({}),
EffectsModule.forRoot([])
We also don't use routing in our app, just component initialization like so :
@ViewChild('transactionContainer', { read: ViewContainerRef, static: true })
public transactionContainer: ViewContainerRef | undefined;
constructor(private readonly injector: Injector) {}
async loadLazyComponent() {
const lazyStoreModuleNgModuleRef = createNgModule(
LazyStoreModule,
this.injector
);
this.transactionContainer!.createComponent(LazyStoreComponent, {
ngModuleRef: lazyStoreModuleNgModuleRef,
});
}
If I declare the store in the app.module.ts, It solves the problem but we can't declare the store there because we need our stores to be isolated in each component
I made a stackblitz : https://stackblitz.com/edit/angular-ivy-u9b5yo?file=package.json
If you downgrade to 13.2.0, we have no issues anymore.
Minimal reproduction of the bug/regression with instructions
The store to be correctly provided
Versions of NgRx, Angular, Node, affected browser(s) and operating system(s)
NgRx: 14.3.2 Angular 14.2.12 Node 16
Other information
Thank you in advance ! :-)
I would be willing to submit a PR to fix this issue
- [X] Yes
- [X] No
We have the same problem in our project. Are there any news?
Same Problem on our side. Please let us know. Thanks
I tryed the 15.1.0 version and we still have the same problem
Just ran into this as well. Anyone solve it yet?
We did not change anything with how injection works between those versions so it could have been a change in Angular. Will look into it though
We experienced this issue with the 14.3.0 version, we solved it by downgrading to the 14.2.0 version. Maybe it could be related to the change on how the services are provided. Hope it helps!
Thanks @vavon92. That seems like a reasonable workaround for now. In 14.3.x, we did change where the providers are not inlined directly in the providers
array to add support for standalone provider APIs, but to me, that should not have caused a breakage here.
Indeed, a breaking change was introduced in v14.3 in case root store providers are not provided at the root injector level (in AppModule
or another eagerly loaded module).
To fix this issue, move the StoreModule.forRoot
call to AppModule.imports
: https://stackblitz.com/edit/angular-ivy-den6xd?file=src%2Fapp%2Fapp.module.ts
That's a temporary fix right? The whole point of lazy loading is that I don't need the store in my other modules
That's a temporary fix right? The whole point of lazy loading is that I don't need the store in my other modules
I hope so, we can't update to the v15 otherwise! It's quite blocking on our side as we will update to angular 15 soon (requiring v15 for ngrx)
@vavon92 I just tried to downgrade to 14.2.0 in my stackblitz and I still have the issue
@Julienbideau We tested in on our production environment and it works, I don't have any more insights on that sorry Maybe you could try downgrading to the 14.0.0 and see if that works out for you
@Julienbideau Checkout your package-lock.json
file, make sure you have installed 14.2.0 version.
If you run ng update
it will install 14.3.3 version, I fixed this with npm install
instead.
I have a very large application that I'm going to incrementally move over to the new Angular paradigms and I'm running into this same issue. I have some common state shared between lazy loaded features and I declare that via imports
in the application NgModule
using StoreModule.forRoot()
. Then each older lazy loaded feature uses StoreModule.forChild()
which works fine. I started creating a new feature module today using standalone components, routes with providers, and provideState()
and as soon as I navigate to that route, I get the same error as above: NullInjectorError: NullInjectorError: No provider for InjectionToken @ngrx/store Root Store Provider!
.
My app is using Angular 14 and ngrx 14, but I was able to put together a small-ish reproduction to demonstrate this is still happening in Angular 15 and ngrx 15:
https://stackblitz.com/edit/angular-wzciju
I guess the issue comes from the new provide_store.ts added in 14.3.0 https://github.com/ngrx/platform/compare/14.2.0...14.3.0
I didn't found the issue yet
@Julienbideau I haven't either. I suspect it's a race condition somewhere.
@bryanforbes It's not a race condition. In your case, you're mixing two different ways of registering the Store
. provideStore
has providers that StoreModule.forRoot()
does not have.
This works
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { provideStore, StoreModule } from '@ngrx/store';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { commonReducer, commonFeatureName } from './common';
@NgModule({
declarations: [AppComponent],
imports: [
CommonModule,
BrowserModule,
AppRoutingModule,
// StoreModule.forRoot({ [commonFeatureName]: commonReducer }), <-- Does not work with provideState()
],
providers: [
provideStore({ [commonFeatureName]: commonReducer }), // works with provideState()
],
bootstrap: [AppComponent],
})
export class AppModule {}
@brandonroberts Thank you for the quick response! Should StoreModule.forFeature()
work with provideStore()
at the root level? I have several feature modules that use StoreModule.forFeature()
and I'm trying to avoid rewriting older code (I'd like to do it incrementally when I have time) as well as avoiding providing two stores.
@bryanforbes yes, it should work also. provideStore()
and includes all the previous providers and tokens, and a few new ones to support environment providers.
@brandonroberts I forked my previous stackblitz, added some feature state to hello.module.ts
, switched to using provideStore()
at the root, and I'm now getting an injector error when loading the hello
route: NullInjectorError: NullInjectorError: No provider for StoreRootModule
https://stackblitz.com/edit/angular-9lezro
@bryanforbes ahh yes, that's correct because of the NgModules. You can put both in your AppModule, all the providers will get merged, so last one wins and you'll get all the necessary dependencies. The state only needs to be registered once though in provideStore()
.
@NgModule({
declarations: [AppComponent],
imports: [
CommonModule,
BrowserModule,
AppRoutingModule,
StoreModule.forRoot(),
],
providers: [provideStore({ [commonFeatureName]: commonReducer })],
bootstrap: [AppComponent],
})
export class AppModule {}
@brandonroberts Perfect! Thanks for helping me debug that. It might be good to add that as an FAQ in the docs.
@brandonroberts will I need to do something similar with EffectsModule.forRoot()
and provideEffects()
in the app module?
@bryanforbes most likely yes, same rule applies though with only registering the root effects once if you have any
A breaking change was introduced in v14.3. I am still curious how come it was working in the early version because it shouldn't. It is recommended to only import StoreModule.forRoot({}) in the root module of your Angular application, not in a lazy-loaded module. This is because StoreModule.forRoot({}) registers the store singleton with the root injector, and you don't want to create multiple instances of the store in your application.
if you want to create a lazy loaded store module for the feature.
Add the StoreModule.forFeature() and EffectsModule.forFeature() methods to the imports array of the new module. For example, you can add the following:
imports: [
CommonModule,
StoreModule.forFeature('books', booksReducer),
EffectsModule.forFeature([BooksEffects]),
],
A fix to this issue would be https://github.com/ngrx/platform/issues/3700#issuecomment-1397683026
Any news about this issue ?
@brandonroberts does a solution exist for actual question posed by the OP? For example, we have a host app that lazy loads a remote app using module federation.
The host app does not use NgRx. The remote app does use NgRx.
The goal is for the host app to not know or even care what the remote app is doing for state management (context: remote app is written by a completely separate team working mostly independently).
But currently because of this NullInjectorError
, we are forced to include NgRx in the host app and do the StoreModule.forRoot
stuff in the AppModule.
--> I really don't want this code in the host app. It is irrelevant to the host app and increases the bundle size of the host app, and may not even get used (i.e. remote module may not get fetched), depending on how the user interacts with the app.
I want this lazy loaded remote module to be completely in charge of it's state management concerns, without having to rely on the host app.
Is there any solution for this?
Indeed, a breaking change was introduced in v14.3 in case root store providers are not provided at the root injector level (in
AppModule
or another eagerly loaded module).To fix this issue, move the
StoreModule.forRoot
call toAppModule.imports
: https://stackblitz.com/edit/angular-ivy-den6xd?file=src%2Fapp%2Fapp.module.ts
this workaround pretty much is not what one can do i think for Module federation use case where shell UI and module federated components are pretty much independent of each others.
@brandonroberts does a solution exist for actual question posed by the OP? For example, we have a host app that lazy loads a remote app using module federation.
The host app does not use NgRx. The remote app does use NgRx.
The goal is for the host app to not know or even care what the remote app is doing for state management (context: remote app is written by a completely separate team working mostly independently).
But currently because of this
NullInjectorError
, we are forced to include NgRx in the host app and do theStoreModule.forRoot
stuff in the AppModule. --> I really don't want this code in the host app. It is irrelevant to the host app and increases the bundle size of the host app, and may not even get used (i.e. remote module may not get fetched), depending on how the user interacts with the app.I want this lazy loaded remote module to be completely in charge of it's state management concerns, without having to rely on the host app.
Is there any solution for this?
Even when including StoreModule.forRoot()
in the host's app module, we still see the same issue when using module federation. What does your webpack.config look like in regards to the shared libs?
Even when including
StoreModule.forRoot()
in the host's app module, we still see the same issue when using module federation. What does your webpack.config look like in regards to the shared libs?
Like this:
let federatedModules = withModuleFederationPlugin({
shared: share({
// angular, rxjs, others
'@ngrx/store': { singleton: true, eager: true, strictVersion: true },
'@ngrx/effects': { singleton: true, eager: true, strictVersion: true },
}),
sharedMappings: [],
});
I believe the remote looks similar, maybe without eager
though.