angular - 通过 Angular 中的工厂打开通用注入
问题描述
我希望能够使用通用工厂注入具有约束到 Angular 中的接口的泛型服务...如果我声明每种注入类型的提供者,我可以做到这一点,但这违背了我为什么要这样做的目的想要这样。
我想要的是这样的:
interface WorkerServiceContract {
doWork(): void;
}
class MyService<T extends WorkerServiceContract> {
constructor(private worker: T) {}
doWorkWrapper() { this.worker.doWork(); }
}
所以在任何时候我都可以做一个:
@Injectable({ providedIn: 'root' })
class FooWorkerService implements WorkerServiceContract {
doWork() { console.log('foo'); }
}
@Injectable({ providedIn: 'root' })
class BarWorkerService implements WorkerServiceContract {
doWork() { console.log('bar'); }
}
@Component(/*blabla*/)
class MyComponent {
constructor(private fooWorker: MyService<FooWorkerService>, private barWorker: MyService<BarWorkerService>) {}
}
我知道我可以为每种可能性使用特定的令牌声明注入WorkerServiceContract
,但我想知道是否有办法(我查看了文档但无法弄清楚)让它“打开”.. .所以我可以做类似的事情(这显然行不通):
providers: [
{
provide: MyService<T extends ServiceWorker>
useFactory: (worker: T) => new MyService<T>(worker);
deps: [T]
}
]
我知道这在提供者定义中是不可能的(因为它不知道T
),但是是否有任何机制可以允许像这样的“通用”工作?这可能是显而易见的事情,但我似乎可以开始做这件事
我正在使用 Angular 9
真实场景(附录)
我们为什么想要这个(现实生活场景)的全部理由是:
我有一个工具生成的服务类(来自 Swagger/OpenApi)。对于该服务,我创建了一个代理/包装服务,它拦截对 API 的所有 http(不是 http,而是方法,这些方法不仅仅是调用 http 客户端)调用,并通过管道将返回的 observables 处理错误(和成功,实际上)显示 UI 通知(并创建对诊断日志服务器的其他调用等)。
这些处理程序有时是通用的,但每个视图(取决于被调用的 API)可能希望有自己的处理程序(例如,一个可能显示一个 toast,一个可能显示一个弹出窗口,一个可能希望转换对 API 的调用一些方法,或其他)。
我可以在每次调用 API 时都这样做,但在我的团队中,我们发现将这些问题(处理成功调用与从 API 接收的正常数据以及错误处理)分开有助于提高可读性和组件代码的大小(以及每个代码的责任)。我们已经通过对构造函数的“简单”调用解决了这个问题,例如:
constructor(private apiService: MyApiService) {
this.apiService = apiService.getProxyService<MyErrorHandler>();
}
它返回一个Proxy
处理所有这些的。这很好用,但我们正在讨论让它“更干净”的想法,如下所示:
constructor(private apiService: MyApiService<MyErrorHandler>) {}
并让 DI 容器上的工厂为我们创建该代理,这将带来以下好处:a) 不必记住对构造函数进行调用,b) 清楚地了解所有依赖项(包括错误处理程序)直接在构造函数参数上(无需深入研究构造函数代码,根据实际组件可能还有其他内容)
不,HttpClient
拦截器在这里不起作用,因为自动生成的服务不仅仅是HttpClient
调用(我们想要处理从该服务返回的内容,而不是直接在HttpResponse
对象上)
解决方案
更新
也许尝试使用令牌的注射器
const services = new Map();
const myService = <T>(service: T): InjectionToken<MyService<T>> => {
if (services.has(service)) {
return services.get(service);
}
const token = new InjectionToken<<T>(t: T) => MyService<T>>(`${Math.random()}`, {
providedIn: 'root',
factory: injector => new MyService(injector.get(service)),
deps: [Injector],
});
services.set(service, token);
return token;
};
class MyComponent {
constructor(
@Inject(myService(FooWorkerService)) private foo: MyService <FooWorkerService>,
@Inject(myService(BarWorkerService)) private bar: MyService <BarWorkerService>,
) {
}
}
原来的
你是对的,泛型在编译后不存在,因此不能用作提供者。
要解决它,您需要在组件中注入工厂本身。因为无论如何您都会指定泛型的类型,所以现在您需要更多代码来实现所需的行为。
您可以使用
const MY_SERVICE_FACTORY = new InjectionToken<<T>(t: T) => MyService<T>>('MY_SERVICE_FACTORY', {
providedIn: 'root',
factory: () => worker => new MyService(worker),
});
// just a helper to extract type, can be omitted
export type InjectionTokenType<Type> = Type extends InjectionToken<infer V> ? V : never;
class MyComponent {
constructor(
@Inject(MY_SERVICE_FACTORY) private serviceFactory: InjectionTokenType<typeof MY_SERVICE_FACTORY>,
foo: FooWorkerService,
bar: BarWorkerService,
) {
this.fooWorker = this.serviceFactory(foo);
this.barWorker = this.serviceFactory(bar);
}
}
还为了保持构造函数中的代码更清晰,您可以将其移动到组件的提供者。
@Component({
// blabla
providers: [
{
provide: 'foo',
useFactory: t => new MyService(t),
deps: [FooWorkerService],
},
{
provide: 'bar',
useFactory: t => new MyService(t),
deps: [BarWorkerService],
},
],
})
class MyComponent {
constructor(
@Inject('foo') private fooWorker: MyService<FooWorkerService>,
@Inject('bar') private barWorker: MyService<BarWorkerService>,
) {}
}
推荐阅读
- conditional - 是否可以在条件语句中使用空手道“匹配”?
- apache-spark - 了解 Spark 版本
- javascript - Canvas2Image 在 IE11 中不起作用
- node.js - 基于 MVC 的项目的 Firebase
- python - Python 3 中的 NP.max 函数错误
- ionic3 - TypeError: Object(...) 不是函数
- asp.net - InvalidCastException:指定的强制转换无效。lambda_method
- r - 具有密度行为的 Parcoordplot
- jenkins - Sonarqube 6.7 - 无法读取 ISSUES.LOCATIONS、com.google.protobuf.InvalidProtocolBufferException
- javascript - JQuery 自动完成错误:找不到未定义的属性“隐藏”