You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular13报StoreModule.forRoot() called twice错误如何解决

问题场景

在Angular 13项目中为实现布局自定义功能,在CoreModule中接入@ngrx/store,项目模块结构如下:
CoreModule

除AppModule外,项目包含以下业务模块:

  • CoreModule
  • AdminModule

布局相关代码存放在CoreModule中,@ngrx/store的相关实现也位于CoreModule内,同时CoreModule被AdminModule导入。


相关代码片段

state.ts

import {UiState} from './ui/state';

export interface AppState {
    auth: any;
    ui: UiState;
}

ui/reducer.ts

import {
    NAVBAR_DARK_VARIANTS,
    NAVBAR_LIGHT_VARIANTS,
    SIDEBAR_DARK_SKINS,
    SIDEBAR_LIGHT_SKINS
} from '../../utils/themes';
import * as Actions from './actions';
import {UiAction} from './actions';
import initialState, {UiState} from './state';

export function uiReducer(state: UiState = initialState, action: UiAction) {
    switch (action.type) {
        case Actions.TOGGLE_SIDEBAR_MENU:
            return {
                ...state,
                menuSidebarCollapsed: !state.menuSidebarCollapsed
            };
        case Actions.TOGGLE_CONTROL_SIDEBAR:
            return {
                ...state,
                controlSidebarCollapsed: !state.controlSidebarCollapsed
            };
        case Actions.TOGGLE_DARK_MODE:
            let variant: string;
            let skin: string;
            if (state.darkMode) {
                variant = NAVBAR_LIGHT_VARIANTS[0].value;
                skin = SIDEBAR_LIGHT_SKINS[0].value;
            } else {
                variant = NAVBAR_DARK_VARIANTS[0].value;
                skin = SIDEBAR_DARK_SKINS[0].value;
            }
            return {
                ...state,
                navbarVariant: variant,
                sidebarSkin: skin,
                darkMode: !state.darkMode
            };
        case Actions.SET_NAVBAR_VARIANT:
            let navbarVariant: string;
            if (state.darkMode) {
                navbarVariant = action.payload || NAVBAR_DARK_VARIANTS[0].value;
            } else {
                navbarVariant =
                    action.payload || NAVBAR_LIGHT_VARIANTS[0].value;
            }
            return {
                ...state,
                navbarVariant
            };
        case Actions.SET_SIDEBAR_SKIN:
            let sidebarSkin: string;
            if (state.darkMode) {
                sidebarSkin = action.payload || SIDEBAR_DARK_SKINS[0].value;
            } else {
                sidebarSkin = action.payload || SIDEBAR_LIGHT_SKINS[0].value;
            }
            return {
                ...state,
                sidebarSkin
            };
        default:
            return state;
    }
}

core.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { AdminHeaderComponent } from './components/layouts/admin-header/admin-header.component';
import { AdminSidebarComponent } from './components/layouts/admin-sidebar/admin-sidebar.component';
import { PageNotFoundComponent } from './components/errors/page-not-found/page-not-found.component';
import { StoreModule } from '@ngrx/store';
import { authReducer } from './store/auth/reducer';
import { uiReducer } from './store/ui/reducer';

@NgModule({
  declarations: [
    AdminHeaderComponent,
    AdminSidebarComponent,
    PageNotFoundComponent,
  ],
  imports: [
    CommonModule,
    RouterModule,
    FormsModule,
    ReactiveFormsModule,
    StoreModule.forRoot({ui: uiReducer}),
  ],
  exports: [
    AdminHeaderComponent,
    AdminSidebarComponent,
    FooterComponent
  ]
})
export class CoreModule { }

app.module.ts

import { ErrorHandler, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { CommonModule } from '@angular/common';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CoreModule } from './core/core.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    CommonModule,
    HttpClientModule,
    CoreModule,
    FormsModule,
    ReactiveFormsModule
  ],
  providers: [
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PageNotFoundComponent } from 'src/app/core/components/errors/page-not-found/page-not-found.component';

const routes: Routes = [
  {
    path: '',
    redirectTo: 'admin',
    pathMatch: 'full'
  },
  { path: 'page-not-found', component: PageNotFoundComponent, data: {breadcrumb: 'Page Not Found'} },
  {
    path: 'admin',
  loadChildren: () => import('./features/admin/admin.module').then(m => m.AdminModule)
  },
  { path: '**',
    redirectTo: 'page-not-found', pathMatch: 'full'
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

报错信息

启动Angular应用时抛出如下错误:

ERROR Error: Uncaught (in promise): TypeError: StoreModule.forRoot() called twice. Feature modules should use StoreModule.forFeature() instead.
TypeError: StoreModule.forRoot() called twice. Feature modules should use StoreModule.forFeature() instead.
at Object._provideForRootGuard [as useFactory] (ngrx-store.mjs:1294:1)

注释掉CoreModule中的StoreModule.forRoot({ui: uiReducer})代码后错误消失。


问题原因

@ngrx/store 明确规定StoreModule.forRoot()只能在整个应用的根模块(即AppModule)中调用一次,作用是初始化全局Store、注册根级reducer。所有非根的特性模块(包括CoreModule、AdminModule这类被导入的模块、懒加载模块)都不能调用forRoot()方法注册Store,否则会触发重复初始化的守卫报错。
当前写法把forRoot()放在了CoreModule中,而CoreModule会被AppModule导入、同时还被懒加载的AdminModule导入,相当于多次执行了Store根初始化逻辑,直接触发报错。


解决方法

按照NgRx的模块注册规则调整代码即可,分两步:

  1. 把StoreModule.forRoot()的调用从CoreModule移到根模块AppModule中,全局只保留这一处forRoot调用
  2. CoreModule中需要注册的状态片段,改用StoreModule.forFeature()注册特性状态

修改后的代码示例

第一步:修改core.module.ts

把原来的StoreModule.forRoot()替换为forFeature()注册ui状态:

// 其他导入保持不变
@NgModule({
  declarations: [
    AdminHeaderComponent,
    AdminSidebarComponent,
    PageNotFoundComponent,
  ],
  imports: [
    CommonModule,
    RouterModule,
    FormsModule,
    ReactiveFormsModule,
    // 特性模块用forFeature注册自己的状态片段
    StoreModule.forFeature('ui', uiReducer),
    // 如果auth状态属于CoreModule的特性状态,同样用forFeature注册
    // StoreModule.forFeature('auth', authReducer)
  ],
  exports: [
    AdminHeaderComponent,
    AdminSidebarComponent,
    FooterComponent
  ]
})
export class CoreModule { }

第二步:修改app.module.ts

在根模块中调用一次StoreModule.forRoot()完成全局Store初始化,全局需要的根reducer、元reducer、runtime检查等配置都在这里传入:

// 其他导入保持不变
import { StoreModule } from '@ngrx/store';
// 如果有authReducer需要作为根状态注册可以在这里引入,否则forRoot可以传空对象
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    CommonModule,
    HttpClientModule,
    // 根模块全局只调用一次forRoot初始化Store
    StoreModule.forRoot({
      // 根级reducer在这里注册,没有的话可以传空对象 {}
      // auth: authReducer
    }),
    CoreModule,
    FormsModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

修改完成后重新启动项目,报错就会消失,状态功能也能正常使用。
补充说明:CoreModule的常规设计是只在AppModule中导入一次,不要在懒加载的特性模块(比如AdminModule)中重复导入CoreModule,否则不仅会重复注册Store相关服务,还会导致CoreModule中声明的服务、组件重复实例化,引发其他隐性问题。


内容的提问来源于stack exchange,提问作者Ayobamilaye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 14:39:53