Angular通用Material自动完成组件构造函数注入令牌报错问题
我实现了一个通用的Material自动完成组件,可适配不同API返回的数据(比如国家、人员、职位等)。这类数据都包含id和name这两个共同属性,所以定义了如下接口:
export interface AutocompleteValue { id?: number, name: string }
同时我用统一结构的服务来获取数据,所有服务都继承自同一个泛型基类,都包含getAll()方法:
export class APIService<GetType> { getAll(): Observable<T[]> {...}; }
基于上述定义,我开发了一个通用Angular Material自动完成组件:
@Component({...}) export class AutoCompleteInput<Type extends AutocompleteValue, ServiceType extends APIService<Type>> implements ControlValueAccessor, MatFormFieldControl<Type | undefined> { ... constructor(protected service: ServiceType, ...) {} ... }
在实际使用时,我通过继承这个通用组件实现了职位自动完成组件:
export class PositionsAutoComplete extends AutoCompleteInput<GetDto, PositionService> { constructor( @Inject(POSITION_SERVICE_TOKEN) protected override service: PositionService, ...){} }
我定义了注入令牌:
export const POSITION_SERVICE_TOKEN = new InjectionToken<PositionService>('');
并在app.component.ts中配置了提供者:
providers: [{ provide: POSITION_SERVICE_TOKEN, useClass: PositionService }]
目前子类的使用完全正常,但父类AutoCompleteInput的构造函数却报出如下错误:
No suitable injection token for parameter 'service' of class
'AutoCompleteInput'. Consider using the @Inject decorator to specify
an injection token.(-992003)
更新:我之前遇到过类似的通用服务问题,当时只要移除泛型类的@Injectable装饰器就能解决。我觉得这次的问题类似——@Component装饰器会触发泛型类的实例化,导致需要注入令牌,但如果移除@Component装饰器,通用组件的模板(autocomplete-input.component.html)就无法使用了。
请问:为什么父类会出现这个报错?该如何解决?
报错原因
Angular的依赖注入系统在处理带@Component装饰器的类时,会尝试解析构造函数中的所有依赖项。你的父类AutoCompleteInput是带有@Component的泛型类,Angular无法推断泛型类型ServiceType对应的注入令牌——因为泛型类型在编译时会被擦除,运行时Angular不知道该注入哪个具体的服务实例,所以抛出了缺少注入令牌的错误。
解决方法
方法一:将父类标记为抽象类
把父类AutoCompleteInput标记为abstract,这样Angular就不会尝试直接实例化它,只会处理作为子类的具体组件。
修改父类定义:
@Component({...}) export abstract class AutoCompleteInput<Type extends AutocompleteValue, ServiceType extends APIService<Type>> implements ControlValueAccessor, MatFormFieldControl<Type | undefined> { ... constructor(protected service: ServiceType, ...) {} ... }
Angular会识别这是一个抽象基类,不会去解析它的依赖注入,只会在实例化子类时处理依赖——而子类已经通过@Inject指定了具体的注入令牌,所以不会再报错。这是最简单直接的方案,完全适配你现有的子类实现。
方法二:用抽象注入令牌统一父类依赖
如果不想把父类设为抽象类,可以定义一个抽象的注入令牌,让父类依赖这个抽象令牌,再在子类中绑定具体的服务实现。
- 定义抽象服务令牌:
export const AUTOCOMPLETE_API_SERVICE = new InjectionToken<APIService<AutocompleteValue>>('Autocomplete API Service');
- 修改父类构造函数,使用抽象令牌注入:
@Component({...}) export class AutoCompleteInput<Type extends AutocompleteValue, ServiceType extends APIService<Type>> implements ControlValueAccessor, MatFormFieldControl<Type | undefined> { ... constructor( @Inject(AUTOCOMPLETE_API_SERVICE) protected service: ServiceType, ... ) {} ... }
- 在子类的提供者配置中,将具体服务映射到抽象令牌:
providers: [ { provide: AUTOCOMPLETE_API_SERVICE, useExisting: POSITION_SERVICE_TOKEN } ]
内容的提问来源于stack exchange,提问作者VPNer

