Search code examples
angularngrxangular-standalone-components

How to use root store in a standalone Component?


I'm working on a project using the old NgModules architecture and I'm transitioning into the new standalone components gradually. I'm stuck right now on how to provide the root store to the standalone component. I'm still bootstrapping the application using app.module so I'm not able to use bootstrapApplication in the app.module. If I use provideStore(reducers, {metaReducers}) I get error that the store was already declared.

app.module

@NgModule({
  declarations: [
    AppComponent,
    ErrorPageComponent,
    FallbackComponent,
    ShouldLoginComponent
  ],
  imports: [
    BrowserAnimationsModule,
    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
    BrowserModule,
    HttpClientModule,
    IdentityModule.forRoot(),
    CoreModule,
    StoreModule.forRoot(reducers, {metaReducers}), <-- the root store
...
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

reducers.ts

export interface AppState {
    activities: ActivityState,
    auth: AuthState,
    chatflows: ChatFlowsState,
    kb: KbState,
    layout: LayoutState,
    properties: PropertiesState,
    router: RouterReducerState<any>;
    savedreplies: SavedRepliesState,
    settings: SettingsState,
    tickets: TicketsState,
    visitors: VisitorState
 }

export const reducers: ActionReducerMap<AppState> = {
    activities: activityReducer,
    auth: authReducer,
    chatflows: chatFlowsReducer,
    kb: KbStateReducer,
    layout: layoutReducer,
    properties: propertiesReducer,
    router: routerReducer,
    savedreplies: savedRepliesReducer,
    settings: settingsReducer,
    tickets: TicketsStateReducer,
    visitors: visitorsReducer
};

export const metaReducers: MetaReducer<AppState>[] = [];

routes

const routes: Routes = [
    {
        path: '',
        component: BaseComponent,
        children: [     
            {
                path: 'conversations',
                canActivate: [IdentityGuardWithForcedLogin],
                loadComponent: () => import('../conversations/conversations.components').then(m => m.ConversationsComponent),
                providers: [
                    provideStore(reducers, {metaReducers}), <-- getting an error duplicate store
                    provideEffects([ActivityEffects, VisitorsEffects, TicketsEffects]),
                ]
            },
            {
                path: 'live',
                canActivate: [IdentityGuardWithForcedLogin],
                loadChildren: () => import('../live/live.module').then(m => m.LiveModule)
            },
            {
                path: 'dashboard',
                canActivate: [IdentityGuardWithForcedLogin],
                load children: () => import('../dashboard/dashboard.module').then(m => m.DashboardModule)
            },

conversations.component.ts


@Component({
    selector: 'conversations',
    templateUrl: './conversations.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush,
    standalone: true,
    imports: [VisitorsComponent, MessagesComponent, InfoComponent]
})
export class ConversationsComponent{
    constructor(
        private store: Store<AppState>, <-- injecting the root store here
        private inboxService: ConversationsService,
        private modalService: NgbModal,
    ) {
...

Solution

  • You can just use provideStore directly in the AppModule.

    @NgModule({
      declarations: [
        AppComponent,
        ErrorPageComponent,
        FallbackComponent,
        ShouldLoginComponent
      ],
      imports: [
        BrowserAnimationsModule,
        BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
        BrowserModule,
        HttpClientModule,
        IdentityModule.forRoot(),
        CoreModule,
        provideStore(reducers, {metaReducers}), // <-- the root store
        provideEffects([ActivityEffects, VisitorsEffects, TicketsEffects]), // <-- the root effects
        ...
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    

    Then we can remove it from the routing:

    const routes: Routes = [
        {
            path: '',
            component: BaseComponent,
            children: [     
                {
                    path: 'conversations',
                    canActivate: [IdentityGuardWithForcedLogin],
                    loadComponent: () => import('../conversations/conversations.components').then(m => m.ConversationsComponent),
                },