Angular向库模块传递环境配置失败:apiUrl为何未定义?
问题描述
尝试将环境配置传递给Angular库模块以访问API地址,但控制台打印的config.apiUrl始终为undefined,相关代码如下:
共享库模块代码
export const CONFIG_TOKEN = new InjectionToken<any>('config'); @NgModule({ declarations: [ AscSharedLibsComponent, BaseTableComponent, ToolbarButtonsComponent, ], imports: [ ReactiveFormsModule, BrowserAnimationsModule, TableModule, InputTextModule, ButtonModule, RippleModule, ToolbarModule, FileUploadModule, TooltipModule, SpeedDialModule, CheckboxModule, StoreModule.forRoot({}), StoreDevtoolsModule.instrument({ maxAge: 40 }), ], exports: [AscSharedLibsComponent, BaseTableComponent], providers: [ { provide: CONFIG_TOKEN, useValue: {} }, ], }) export class AscSharedLibsModule {}
库内TableConfigService服务代码
@Injectable({ providedIn: 'root', }) export class TableConfigService { constructor( private restApi: RequestService, @Inject(CONFIG_TOKEN) private config: any ) { console.log('apiUrl======', this.config.apiUrl); } load(tableId: string) { return this.restApi .request(this.config['apiUrl'], 'GET', '/api/v5/app-table-designs', { 'name.equals': tableId, }) .pipe( map((res: any) => { if (!res.body) return undefined; return JSON.parse(res.body[0]['tableDefinition']); }) ); } }
消费应用模块代码
@NgModule({ declarations: [AppComponent], imports: [BrowserModule, AscSharedLibsModule, TableModule], providers: [ Table, TableService, { provide: CONFIG_TOKEN, useValue: { apiUrl: 'your-api-url' }, }, ], bootstrap: [AppComponent], }) export class AppModule {}
public-api.ts代码
/* * Public API Surface of asc-shared-libs */ export * from './lib/asc-shared-libs.service'; export * from './lib/asc-shared-libs.component'; export * from './lib/asc-shared-libs.module'; export * from './lib/base-table/base-table.component'; export * from './lib/common/utils'; export * from './lib/model/base-table.model'; export * from './lib/services/request.service'; export * from './lib/services/table-config.service'; export * from './lib/services/text.service';
问题根源
注入令牌实例不匹配
Angular的InjectionToken按实例而非字符串名称区分。若消费应用未从库的公共API导入CONFIG_TOKEN,而是自行重新定义同名令牌,会导致库服务注入的是库内的空令牌实例,与应用提供的配置完全无关。库模块提前绑定空值覆盖配置
库模块的providers中已为CONFIG_TOKEN绑定空对象{},当库被导入到应用时,该提供者会被添加到根注入器,优先级覆盖应用模块中提供的配置值,导致服务拿到的始终是空对象。root级服务实例化时机问题
TableConfigService使用providedIn: 'root',会在应用启动初期由根注入器实例化,此时若库模块的提供者已注册,服务会直接注入空值,后续应用模块的配置无法同步更新。
解决方案
步骤1:确保注入令牌在公共API中导出
当前public-api.ts中export * from './lib/asc-shared-libs.module';已包含CONFIG_TOKEN的导出,只需确保消费应用从库中正确导入该令牌,不要自行定义。
步骤2:移除库模块中的默认令牌提供者
修改AscSharedLibsModule,删除providers中的CONFIG_TOKEN默认绑定:
@NgModule({ // ... 其他配置不变 providers: [ // 移除 { provide: CONFIG_TOKEN, useValue: {} } ], }) export class AscSharedLibsModule {}
步骤3:使用forRoot模式传递配置(推荐)
Angular库通常通过forRoot方法接收应用配置,避免注入冲突:
export const CONFIG_TOKEN = new InjectionToken<any>('config'); @NgModule({ // ... 其他配置不变 }) export class AscSharedLibsModule { static forRoot(config: any): ModuleWithProviders<AscSharedLibsModule> { return { ngModule: AscSharedLibsModule, providers: [ { provide: CONFIG_TOKEN, useValue: config } ] }; } }
在消费应用的AppModule中,通过forRoot传递配置:
// 务必从库中导入CONFIG_TOKEN,不要自行定义 import { AscSharedLibsModule, CONFIG_TOKEN } from 'asc-shared-libs'; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, AscSharedLibsModule.forRoot({ apiUrl: 'your-api-url' }), TableModule ], providers: [Table, TableService], bootstrap: [AppComponent], }) export class AppModule {}
步骤4:验证服务注入的令牌正确性
确保TableConfigService中导入的CONFIG_TOKEN来自库的公共API:
import { Injectable, Inject } from '@angular/core'; import { CONFIG_TOKEN } from '../asc-shared-libs.module'; import { RequestService } from './request.service'; @Injectable({ providedIn: 'root', }) export class TableConfigService { constructor( private restApi: RequestService, @Inject(CONFIG_TOKEN) private config: any ) { console.log('apiUrl======', this.config.apiUrl); // 现在能正常获取配置值 } // ... 其他代码 }
可选:添加默认配置
若需要给配置设置默认值,可使用@Optional()装饰器:
@Injectable({ providedIn: 'root', }) export class TableConfigService { constructor( private restApi: RequestService, @Inject(CONFIG_TOKEN) @Optional() private config: any = { apiUrl: 'default-api-url' } ) { console.log('apiUrl======', this.config.apiUrl); } }
内容的提问来源于stack exchange,提问作者Shah Rukh

