如何在Angular中通过依赖注入传输泛型类型数据?
我的应用需要在同一个标准组件中展示不同业务对象的数据。假设存在Angular组件EditorComponent<T extends object>,它通过组件Outlet实例化,接收的自定义注入器已经用各业务对象类型指定的提供者完成初始化,这些提供者由以下接口定义:
interface IBusinessObjectInfo { readonly providers?: StaticProvider[] }
具体的业务对象类型由独立的业务模块提供(框架不需要知晓具体类型)。针对每个业务对象类型,需要执行如下操作:
boInfo: IBusinessObjectInfo = createBusinessObjectInfo({ objectRestrictions: { ... }, ... });
其中createBusinessObjectInfo函数接收的参数符合以下泛型接口:
interface ITypedBusinessObjectInfo<T extends object> { readonly objectRestrictions: IObjectRestrictions<T>; ... }
也就是说,传入createBusinessObjectInfo的对象是和业务对象类型绑定的泛型类型。现在需要让createBusinessObjectInfo根据传入的信息生成带有合适类型注入令牌的提供者。
在EditorComponent<T>中,我需要注入这些对象,同时实例化其他基于业务对象的泛型框架类,示例如下:
@Component(...) class EditorComponent<T extends object> { public constructor() { this.restrictionViewModel = new RestrictionViewModel<T>(this.objectRestrictions); } public readonly restrictionViewModel: RestrictionViewModel<T>; private readonly objectRestrictions: IObjectRestrictions<T> = inject(...); }
目前流程都正常,但核心问题是:如何获取泛型类型的注入令牌?
我可以定义无类型的注入令牌:
export const objectRestrictionToken = new InjectionToken('object restrictions');
然后在使用时做类型转换,或者用函数封装(写法更简洁):
export function getObjectRestrictionToken<T extends object>(): InjectionToken<IObjectRestrictions<T>> { return <InjectionToken<IObjectRestrictions<T>>>objectRestrictionToken; }
但这种显式类型转换的方案并不理想,而且调用getObjectRestrictionToken<T>时必须显式指定T也很麻烦。
有没有其他方案,既能解决问题,又不破坏泛型框架和具体业务模块的分离?
方案1:泛型工厂生成唯一注入令牌
创建泛型函数为每个业务类型生成专属的注入令牌,保留类型信息的同时避免类型转换:
// 框架层定义泛型令牌生成器 export function createObjectRestrictionToken<T extends object>(): InjectionToken<IObjectRestrictions<T>> { return new InjectionToken<IObjectRestrictions<T>>(`Object restrictions for business type`); } // 业务模块中针对具体类型生成令牌(示例) interface User { id: number; name: string; } const userRestrictionToken = createObjectRestrictionToken<User>(); // 改造createBusinessObjectInfo,绑定泛型参数生成提供者 function createBusinessObjectInfo<T extends object>(info: ITypedBusinessObjectInfo<T>): IBusinessObjectInfo { const token = createObjectRestrictionToken<T>(); return { providers: [ { provide: token, useValue: info.objectRestrictions } ] }; }
在EditorComponent<T>中,通过注入器上下文获取对应令牌实例:
@Component(...) class EditorComponent<T extends object> { private readonly objectRestrictions: IObjectRestrictions<T>; public readonly restrictionViewModel: RestrictionViewModel<T>; constructor(injector: Injector) { // 令牌实例需与业务模块生成的一致,可通过注入器上下文传递 const token = injector.get<InjectionToken<IObjectRestrictions<T>>>('OBJECT_RESTRICTION_TOKEN_KEY'); this.objectRestrictions = injector.get(token); this.restrictionViewModel = new RestrictionViewModel<T>(this.objectRestrictions); } }
方案2:类型标记+自定义提供者
为业务对象添加唯一类型标记,结合带类型的接口实现类型安全注入,无需显式指定泛型参数:
// 框架层扩展接口,添加类型标记 interface IObjectRestrictionsWithType<T extends object> extends IObjectRestrictions<T> { readonly type: symbol; } // 生成唯一类型标记的工具函数 function createTypeToken<T>(): symbol { return Symbol(`BusinessObjectType`); } // 改造createBusinessObjectInfo,自动添加标记并生成提供者 function createBusinessObjectInfo<T extends object>(info: ITypedBusinessObjectInfo<T>): IBusinessObjectInfo { const typeToken = createTypeToken<T>(); const restrictedInfo: IObjectRestrictionsWithType<T> = { ...info.objectRestrictions, type: typeToken }; return { providers: [ { provide: IObjectRestrictionsWithType, useValue: restrictedInfo }, { provide: 'BUSINESS_OBJECT_TYPE', useValue: typeToken } ] }; }
在EditorComponent<T>中直接注入带类型标记的实例:
@Component(...) class EditorComponent<T extends object> { public readonly restrictionViewModel: RestrictionViewModel<T>; constructor(@Inject(IObjectRestrictionsWithType) private readonly objectRestrictions: IObjectRestrictionsWithType<T>) { this.restrictionViewModel = new RestrictionViewModel<T>(this.objectRestrictions); } }
方案3:泛型InjectionToken+简洁类型断言
利用Angular原生的泛型InjectionToken支持,结合简洁的类型断言实现类型安全注入,同时保持框架与业务模块分离:
// 框架层定义泛型注入令牌 export const ObjectRestrictionToken = new InjectionToken<IObjectRestrictions<unknown>>('Object restrictions'); // 改造createBusinessObjectInfo,绑定泛型值 function createBusinessObjectInfo<T extends object>(info: ITypedBusinessObjectInfo<T>): IBusinessObjectInfo { return { providers: [ { provide: ObjectRestrictionToken, useValue: info.objectRestrictions } ] }; }
在EditorComponent<T>中通过泛型inject获取实例:
@Component(...) class EditorComponent<T extends object> { private readonly objectRestrictions: IObjectRestrictions<T> = inject(ObjectRestrictionToken) as IObjectRestrictions<T>; public readonly restrictionViewModel: RestrictionViewModel<T>; constructor() { this.restrictionViewModel = new RestrictionViewModel<T>(this.objectRestrictions); } }
这种方案仅用到一次类型断言,比显式转换更简洁,且业务模块只需关注自身的objectRestrictions实现,框架无需知晓具体业务类型。
内容的提问来源于stack exchange,提问作者F-H

