Angular+TS中如何配置DI注入服务接口的不同实现
Angular 11 同组件兼容多相似服务的DI实现方案
核心逻辑:TypeScript 接口在编译后会被完全擦除,Angular DI 系统无法直接以接口作为依赖令牌,用抽象类作为公共契约是最适配Angular DI的实现方式,同时能完整保留TS的类型校验能力。
1. 抽离公共服务契约
先定义抽象基类,统一两个服务的公共方法签名,S2独有的方法标记为可选,这个抽象类同时作为DI的依赖令牌使用:
export abstract class BaseCommonService { abstract GetName(): string; abstract GetAge(): number; abstract GetGender(): string; // S2独有方法标记为可选,兼容S的实现 GetGroup?(): string; }
让原有服务S和新服务S2都继承这个抽象类,实现对应方法:
@Injectable() export class S extends BaseCommonService { GetName(): string { /* 原有业务逻辑 */ } GetAge(): number { /* 原有业务逻辑 */ } GetGender(): string { /* 原有业务逻辑 */ } // 无需实现GetGroup方法 } @Injectable() export class S2 extends BaseCommonService { GetName(): string { /* 新业务逻辑 */ } GetAge(): number { /* 新业务逻辑 */ } GetGender(): string { /* 新业务逻辑 */ } GetGroup(): string { /* 独有业务逻辑 */ } }
2. 修改组件C的依赖声明
组件C不再依赖具体服务类,直接依赖抽象契约即可,TS会自动做类型推导:
@Component({ selector: 'app-c', templateUrl: './c.component.html' }) export class ComponentC { // 注入时用抽象类作为令牌,类型自动识别 constructor(public service: BaseCommonService) {} }
你原有模板的适配逻辑可以保留,注意修正一处写法错误:
<!-- 注意GetGroup是方法,要加()调用,否则会直接输出函数本身而非返回值 --> <div *ngIf="service?.GetGroup"> {{service.GetGroup()}} </div>
3. 按场景配置服务提供者
根据组件的使用场景,在对应注入层级配置要注入的具体服务实现即可,两种最常用的场景:
- 路由级配置(两个相似页面对应不同路由):直接在路由定义中给组件配置对应的服务提供者,不同路由下的组件C会自动拿到对应服务实例
const routes: Routes = [ { path: 'original-page', component: ComponentC, providers: [{ provide: BaseCommonService, useClass: S }] }, { path: 'new-page', component: ComponentC, providers: [{ provide: BaseCommonService, useClass: S2 }] } ];
- 同页面多实例场景:如果同一个页面需要同时放置两个组件C实例,分别对接S和S2,更灵活的方式是用
@Input传入服务实例,不需要走DI层级配置
// 组件C修改为Input传入服务,移除构造函数中的服务注入 export class ComponentC { @Input() service!: BaseCommonService; }
父组件中先注入S、S2的实例,直接通过属性绑定传入即可:
<app-c [service]="sService"></app-c> <app-c [service]="s2Service"></app-c>
如果你不想改动原有S、S2的继承关系,也可以自定义
InjectionToken作为DI令牌,同时手动声明服务类型为对应接口,但用抽象类的方案不需要额外维护令牌和类型的映射,代码量更少,类型约束也更直接。
内容的提问来源于stack exchange,提问作者qkhanhpro
相关产品推荐
相关产品推荐

