platform icon indicating copy to clipboard operation
platform copied to clipboard

Standalone API in module-based apps: StoreFeatureModule exception

Open mauriziocescon opened this issue 1 year ago • 8 comments

Which @ngrx/* package(s) are the source of the bug?

store

Minimal reproduction of the bug/regression with instructions

Standalone API in module-based apps: imagine you have a reusable module where some ngrx actions / reducers are defined (typical example: shared-module). Since it's a reusable one, it's imported by other ones (route-based and app modules).

Now, it seems to me this is not working fine as long as provideState is used. In particular there's an exception in the StoreFeatureModule constructor for modules importing the shared one (featureReducers = []).

This is an example you can check it out (my reusable module is called CoreModule):

@NgModule({
  imports: [
    // WORKING FINE
    StoreModule.forFeature(coreFeature),
    EffectsModule.forFeature(coreEffects),
  ],
   ...
  providers: [
    // NOT WORKING: exception in the console
    // provideState(coreFeature),
    // provideEffects(coreEffects),
  ],
  ...
})
export class CoreModule {
}

Consider this a reusable module, imported by other route-based ones (in my example InstanceDetailModule and InstanceListModule) and AppModule.

Here are some screenshots:

Screenshot 2023-10-23 at 11 30 17 Screenshot 2023-10-23 at 11 31 23

Expected behavior

For big enterprise applications, there's no other way than mixing and matching different styles of code. Therefore:

StoreModule.forFeature(...),
EffectsModule.forFeature(...),
provideState(...),
provideEffects(...),

should be interchangeable and provideState should work fine when called multiple times with the same data (as StoreModule.forFeature does).

Versions of NgRx, Angular, Node, affected browser(s) and operating system(s)

NgRx: ^16.0.0 Angular: ^16.0.0 Node: ^18.0.0 Browser(s): any Operating systems): OSX

Other information

No other info

I would be willing to submit a PR to fix this issue

  • [ ] Yes
  • [X] No

mauriziocescon avatar Oct 23 '23 09:10 mauriziocescon

I experience similar issues when mixing the classic NgRx state APIs with the standalone NgRx state APIs.

In my minimal reproducible example we see that replacing StoreModule.forFeature with provideState or EffectsModule.forFeature with provideEffects in an Angular feature module results in a NullInjectorError:

Error: Uncaught (in promise): NullInjectorError: NullInjectorError: No provider for InjectionToken @ngrx/store Root Store Provider!

This example uses platformBrowser().bootstrapModule(AppModule), a classic AppComponent, and a classic (NgModule-based) Angular feature.

LayZeeDK avatar Oct 24 '23 07:10 LayZeeDK

Related https://github.com/ngrx/platform/issues/4063

LayZeeDK avatar Oct 24 '23 07:10 LayZeeDK

@LayZeeDK not sure it's the same case... IMO in your AppModule you're missing some mandatory providers (which are defined in my case):

@NgModule({
  bootstrap: [AppComponent],
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpClientModule,
    RouterModule.forRoot(routes),
    StoreModule.forRoot(
      {},
      {
        runtimeChecks: {
          strictStateImmutability: true,
          strictActionImmutability: true,
          strictStateSerializability: true,
          strictActionSerializability: true,
          strictActionWithinNgZone: true,
          strictActionTypeUniqueness: true,
        },
      }
    ),
    StoreDevtoolsModule.instrument({ maxAge: 25 }),
    EffectsModule.forRoot(),
    RouterLink,
    RouterOutlet,
  ],
  providers: [
    provideStore(),
    provideEffects(),
  ],
})
export class AppModule {}

https://ngrx.io/guide/store/reducers#standalone-api-in-module-based-apps

mauriziocescon avatar Oct 24 '23 07:10 mauriziocescon

I checked also #4063 and it seems to me it's a little bit different: my setup (AppModule) is module-based... so I guess the error might be the same (as the root cause) but according to the doc... well it seems to me it shouldn't fail.

The main point is: in a huge module-based app, I think it's pretty natural start any code migration from reusable-modules. I believe It's also natural avoid changing the way these modules are consumed (the imports). And since they are reusable, they can be imported by other modules everywhere.

mauriziocescon avatar Oct 24 '23 08:10 mauriziocescon

Thank you for the reference to NgRx Store Standalone API in module-based apps and NgRx Effects Standalone API in module-based apps, @mauriziocescon.

As seen in this StackBlitz fork, duplicating arguments between StoreModule.forRoot and provideStore as well as EffectsModule.forRoot and provideEffects in AppModule.imports and AppModule.providers solves my issue

LayZeeDK avatar Oct 24 '23 14:10 LayZeeDK

Just in case, this is an easier example to check https://stackblitz.com/edit/stackblitz-starters-pculha?file=src%2Fapp%2Fshared.module.ts

mauriziocescon avatar Oct 30 '23 21:10 mauriziocescon