如何使非懒加载模块的Angular服务仅模块作用域化?
Angular模块作用域服务的问题分析与解决方案
为什么UtilsService变成了全局单例?
Angular的注入器体系分为根注入器和模块注入器两类:
- 根注入器是应用的顶级注入器,所有非懒加载模块的
providers(包括通过providedIn: 模块声明的服务)都会被合并到根注入器中。 - 当你把
SharedModule直接导入根模块AppModule时,UtilsService通过providedIn: SharedModule声明,会被自动注册到根注入器。这就导致整个应用中任何地方注入UtilsService,都会拿到根注入器里的同一个实例,自然变成全局单例。
注意:只有懒加载模块会创建独立的模块注入器,其providers不会合并到根注入器,所以懒加载场景下服务天然是模块级单例。
非懒加载模块场景的实现方案
要让非懒加载模块各自拥有独立的UtilsService实例,需要通过静态工厂方法让每个导入SharedModule的模块,为自己注册独立的服务实例,具体步骤如下:
1. 修改UtilsService的提供方式
去掉providedIn配置,改为通过模块工厂方法提供:
@Injectable() export class UtilsService { constructor() { console.log("UtilsService实例创建"); } }
2. 改造SharedModule,添加静态工厂方法
通过forRoot()和forChild()方法,让根模块和其他非懒加载模块分别获取带有独立服务实例的模块配置,同时添加重复导入检查避免错误:
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core'; import { FilterComponent } from './filter.component'; import { UtilsService } from './utils.service'; @NgModule({ declarations: [FilterComponent], exports: [FilterComponent] }) export class SharedModule { // 禁止同一模块层级重复导入SharedModule constructor(@Optional() @SkipSelf() parentModule: SharedModule) { if (parentModule) { throw new Error('SharedModule已在当前层级导入,请不要重复导入'); } } // 供根模块调用,注册根级的UtilsService实例 static forRoot(): ModuleWithProviders<SharedModule> { return { ngModule: SharedModule, providers: [UtilsService] }; } // 供其他非懒加载模块调用,注册当前模块级的UtilsService实例 static forChild(): ModuleWithProviders<SharedModule> { return { ngModule: SharedModule, providers: [UtilsService] }; } }
3. 在各模块中导入改造后的SharedModule
- 根模块
AppModule调用forRoot():
@NgModule({ declarations: [AppComponent], imports: [ BrowserModule, SharedModule.forRoot(), CommonModule ], bootstrap: [AppComponent] }) export class AppModule { }
- 其他非懒加载模块(如
FeatureModule)调用forChild():
@NgModule({ declarations: [FeatureComponent], imports: [ CommonModule, SharedModule.forChild() ] }) export class FeatureModule { }
效果说明
- 根模块
AppModule及其下属组件,会使用forRoot()提供的UtilsService实例; - 每个调用
forChild()的非懒加载模块,都会拥有自己独立的UtilsService实例; SharedModule中的FilterComponent,在哪个模块中被使用,就会注入该模块对应的UtilsService实例,实现模块级的隔离。
内容的提问来源于stack exchange,提问作者tlq
相关产品推荐
相关产品推荐

