ngrx / platform

Reactive State for Angular
https://ngrx.io
Other
8.01k stars 1.97k forks source link

Standalone API in module-based apps: StoreFeatureModule exception #4092

Open mauriziocescon opened 11 months ago

mauriziocescon commented 11 months ago

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

LayZeeDK commented 11 months ago

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 commented 11 months ago

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

mauriziocescon commented 11 months ago

@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 commented 11 months ago

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.

LayZeeDK commented 11 months ago

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

mauriziocescon commented 10 months ago

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

prewk commented 1 week ago

When I duplicate all the things, module + standalone, the dev tooling gets really weird.

Clicking it once on its addon icon (Chrome) shows everything correct. Clicking it again - Just spinning forever.