继承和依赖注入

我有一套角度2组件,都应该得到一些服务注入。我的第一个想法是,最好创建一个超类并在那里注入服务。然后,我的任何组件都会扩展这个超类,但是这种方法不起作用。

简单的例子:

export class AbstractComponent {
constructor(private myservice: MyService) {
// Inject the service I need for all components
}
}


export MyComponent extends AbstractComponent {
constructor(private anotherService: AnotherService) {
super(); // This gives an error as super constructor needs an argument
}
}

我可以通过在每个组件中注入 MyService来解决这个问题,然后在 super()调用中使用这个参数,但这肯定是某种荒谬的做法。

如何正确组织我的组件,以便它们从超类继承服务?

53208 次浏览

我可以通过在每个组件中注入 MyService 来解决这个问题,并将这个参数用于 super ()调用,但这肯定是某种荒谬的做法。

这并不荒谬,这就是构造函数和构造函数注入的工作方式。

每个可注入类都必须将依赖项声明为构造函数参数,如果超类也有依赖项,那么这些依赖项也需要列在子类的构造函数中,并通过 super(dep1, dep2)调用传递给超类。

传递注入器和强制获取依赖关系有严重的缺点。

它隐藏了使代码难以阅读的依赖关系。
它违背了一个熟悉 Angular2DI 工作原理的人的期望。
它中断生成静态代码的离线编译,以替换声明式和命令式 DI,从而提高性能并减少代码大小。

更新的解决方案,防止使用全局注入器生成多个 myService 实例。

import {Injector} from '@angular/core';
import {MyServiceA} from './myServiceA';
import {MyServiceB} from './myServiceB';
import {MyServiceC} from './myServiceC';


export class AbstractComponent {
protected myServiceA:MyServiceA;
protected myServiceB:MyServiceB;
protected myServiceC:MyServiceC;


constructor(injector: Injector) {
this.settingsServiceA = injector.get(MyServiceA);
this.settingsServiceB = injector.get(MyServiceB);
this.settingsServiceB = injector.get(MyServiceC);
}
}


export MyComponent extends AbstractComponent {
constructor(
private anotherService: AnotherService,
injector: Injector
) {
super(injector);


this.myServiceA.JustCallSomeMethod();
this.myServiceB.JustCallAnotherMethod();
this.myServiceC.JustOneMoreMethod();
}
}

这将确保 MyService 可以在任何扩展 AbstractComponent 的类中使用,而不需要在每个派生类中注入 MyService。

这个解决方案有一些缺点(见我最初的问题下面@G ünter Zöchbauer 的评论) :

  • 注射全局注射器只有在需要在许多地方注射几种不同的服务时才是一种改进。如果只有一个共享服务,那么在派生类中注入该服务可能更好/更容易
  • 我的解决方案和他提出的替代方案都有缺点,它们使得我们更难看出哪个类依赖于哪种服务。

有关 Angular2依赖注入的详细解释,请看这篇博客文章,它对我解决这个问题大有帮助: http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html

我没有手动注入所有的服务,而是创建了一个提供服务的类,例如,它将注入服务。然后将此类注入到派生类中并传递给基类。

派生类别:

@Component({
...
providers: [ProviderService]
})
export class DerivedComponent extends BaseComponent {
constructor(protected providerService: ProviderService) {
super(providerService);
}
}

基本类别:

export class BaseComponent {
constructor(protected providerService: ProviderService) {
// do something with providerService
}
}

提供服务类别:

@Injectable()
export class ProviderService {
constructor(private _apiService: ApiService, private _authService: AuthService) {
}
}

如果父类是从第三方插件获得的(而且不能更改源代码) ,那么可以这样做:

import { Injector } from '@angular/core';


export MyComponent extends AbstractComponent {
constructor(
protected injector: Injector,
private anotherService: AnotherService
) {
super(injector.get(MyService));
}
}

或者最好的方法(在构造函数中只保留一个参数) :

import { Injector } from '@angular/core';


export MyComponent extends AbstractComponent {
private anotherService: AnotherService;


constructor(
protected injector: Injector
) {
super(injector.get(MyService));
this.anotherService = injector.get(AnotherService);
}
}

而不是注入一个将所有其他服务作为依赖项的服务,如下所示:

class ProviderService {
constructor(private service1: Service1, private service2: Service2) {}
}


class BaseComponent {
constructor(protected providerService: ProviderService) {}


ngOnInit() {
// Access to all application services with providerService
this.providerService.service1
}
}


class DerivedComponent extends BaseComponent {
ngOnInit() {
// Access to all application services with providerService
this.providerService.service1
}
}

我将跳过这个额外的步骤,简单地在 BaseComponent 中添加注入所有服务,如下所示:

class BaseComponent {
constructor(protected service1: Service1, protected service2: Service2) {}
}


class DerivedComponent extends BaseComponent {
ngOnInit() {
this.service1;
this.service2;
}
}

这种技术假设了两件事:

  1. 您的担心完全与组件继承有关。最有可能的原因,你降落在这个问题是因为压倒性的非干(湿?)需要在每个派生类中重复的代码。如果您想为所有组件 及服务获得单一入口点的好处,那么您需要做额外的步骤。

  2. 每个组件都扩展了 BaseComponent

如果您决定使用派生类的构造函数,也会有一个缺点,因为您需要调用 super()并传入所有依赖项。虽然我没有看到一个用例需要使用 constructor而不是 ngOnInit,但是这样的用例存在是完全有可能的。

根据我的理解,要从基类继承,首先需要实例化它。为了实例化它,您需要传递它的构造函数所需的参数,因此您通过 super ()调用将它们从子级传递给父级,这样就有意义了。注射器当然是另一个可行的解决方案。

丑陋的黑客

一段时间以前,我的一些客户想加入两个大角度项目到昨天(角度 v4到角度 v8)。Project v4对每个组件使用 BaseView 类,并且它包含用于翻译的 tr(key)方法(在 v8中,我使用 n- 翻译)。因此,为了避免切换翻译系统和编辑数百个模板(在 v4中)或并行安装2翻译系统,我使用了如下丑陋的黑客技术(我并不引以为豪)-在 AppModule类中,我添加了以下构造函数:

export class AppModule {
constructor(private injector: Injector) {
window['UglyHackInjector'] = this.injector;
}
}

现在你可以使用 AbstractComponent

export class AbstractComponent {
private myservice: MyService = null;


constructor() {
this.myservice = window['UglyHackInjector'].get(MyService);
}
}

据我所知,使用 inject() https://angular.io/api/core/inject的 Angular v14现在可以非常简单地实现这一点,因为您现在可以在构造函数之外的“字段初始化器”中设置一个依赖项。

我使用了一些简单的令牌依赖关系,例如 DOCUMENTLOCALE_ID

import { DOCUMENT } from '@angular/common';
import { inject } from '@angular/core';


export abstract class AbstractComponent {
abstractDependency = inject(DOCUMENT);
}
import { Component, inject, LOCALE_ID } from '@angular/core';
import { AbstractComponent } from './abstract.component';


@Component({
selector: 'my-app',
template: '\{\{ abstractDependency }} \{\{ myDependency }}',
})
export class MyComponent extends AbstractComponent {
myDependency = inject(LOCALE_ID);
}

实例: https://stackblitz.com/edit/angular-ivy-zvlx1d?file=src/app/my.component.ts