Angular注入自定义PathLocationStrategy出现重复实例化问题
项目中自定义了继承自PathLocationStrategy的路由策略实现,代码如下:
@Injectable() // 也尝试过 @Injectable({providedIn: 'root'}) export class CustomPathLocationStrategy extends PathLocationStrategy { constructor() { console.log('CustomPathLocationStrategy constructor'); } // 其余自定义逻辑 .... }
为了让Angular识别该自定义实现,在app.module.ts中添加了如下提供者配置:
providers: [ { provide: LocationStrategy, useClass: CustomPathLocationStrategy } ]
上述配置本身可以正常运行,但当尝试将CustomPathLocationStrategy直接注入到组件中时:
@Component(...) export class AppComponent { constructor(private custom: CustomPathLocationStrategy) {} }
浏览器控制台会打印两次CustomPathLocationStrategy constructor日志,说明CustomPathLocationStrategy被创建了两个独立实例,注入到组件的实例与作为LocationStrategy实现生效的实例并非同一个。
疑问:Angular是否不支持在自定义组件或其他服务中注入自定义的LocationStrategy实现?还是当前配置方式存在错误?
配置方式存在错误。Angular依赖注入系统是按注入token匹配对应提供者的:你配置的{ provide: LocationStrategy, useClass: CustomPathLocationStrategy }规则,仅在组件/服务请求注入LocationStrategy这个token时,才会实例化CustomPathLocationStrategy返回。
当你直接把CustomPathLocationStrategy类本身作为注入token请求依赖时,Angular会匹配这个token对应的独立提供者:要么走@Injectable装饰器里providedIn: 'root'的规则单独创建实例,要么在当前作用域找对应类的提供者生成新实例,这就是出现两个独立实例的根本原因。
哪怕给CustomPathLocationStrategy加了providedIn: 'root'也不会解决问题,这个配置相当于在根注入器给CustomPathLocationStrategy这个token单独注册了一个提供者,和LocationStrategy对应的提供者是完全独立的两个绑定,自然会生成两个不同的实例。
额外提醒:你示例里写的空构造函数不符合要求,
PathLocationStrategy的构造函数依赖PlatformLocation和可选的APP_BASE_HREF参数,空构造调用父类构造时不传参会直接抛运行时错误,正确的构造函数写法参考:constructor( _platformLocation: PlatformLocation, @Optional() @Inject(APP_BASE_HREF) href?: string ) { super(_platformLocation, href); console.log('CustomPathLocationStrategy constructor'); }
两种修复方式二选一即可:
- 方式一:添加别名提供者,让两个token指向同一个实例
在app.module.ts的providers数组中新增一条useExisting配置,将CustomPathLocationStrategy作为LocationStrategy的别名,复用同一个实例:
配置完成后,不管是注入providers: [ { provide: LocationStrategy, useClass: CustomPathLocationStrategy }, // 新增以下配置 { provide: CustomPathLocationStrategy, useExisting: LocationStrategy } ]LocationStrategy还是直接注入CustomPathLocationStrategy,Angular都会返回同一个实例,不会重复创建。 - 方式二:遵循依赖倒置原则,统一注入
LocationStrategy抽象类
不需要额外加提供者配置,所有需要使用路由策略的地方都注入LocationStrategy,如果需要调用自定义类独有的方法,做个类型断言即可:
这种方式耦合度更低,后续如果要替换路由策略实现,不需要修改所有注入位置的代码。@Component(...) export class AppComponent { constructor(private locationStrategy: LocationStrategy) { // 调用自定义方法时做类型转换 const customStrategy = this.locationStrategy as CustomPathLocationStrategy; } }
内容的提问来源于stack exchange,提问作者BigJ

