继承和依赖注入

Inheritance and dependency injection

我有一组 angular2 组件,它们都应该注入一些服务。我的第一个想法是最好创建一个超级 class 并在那里注入服务。我的任何组件都会扩展那个 superclass 但这种方法不起作用。

简化示例:

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() 调用来解决这个问题,但这绝对有些荒谬。

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

I could solve this by injecting MyService within each and every component and use that argument for the super() call but that's definetly some kind of absurd.

这并不荒谬。这就是构造函数和构造函数注入的工作原理。

每个可注入的 class 都必须将依赖项声明为构造函数参数,如果 superclass 也有依赖项,则这些依赖项也需要在 subclass' 构造函数中列出,并且通过 super(dep1, dep2) 调用传递给 superclass。

绕过注入器并强制获取依赖项有严重的缺点。

它隐藏了使代码更难阅读的依赖项。
它违反了熟悉 Angular2 DI 工作原理的人的期望。
它打破了生成静态代码的离线编译,以取代声明式和命令式 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 的 class 中使用,而无需在每个派生的 class.

中注入 MyService

这个解决方案有一些缺点(请参阅我的原始问题下方@Günter Zöchbauer 的评论):

  • 只有当有多个不同的服务需要在许多地方注入时,注入全局注入器才是一种改进。如果您只有一项共享服务,那么可能 better/easier 将该服务注入派生的 class(es)
  • 我的解决方案和他提出的替代方案都有一个缺点,那就是它们让我们更难看出哪个 class 取决于哪个服务。

有关 Angular2 中依赖注入的非常好的书面解释,请参阅此博客 post 这对我解决问题有很大帮助:http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html

我没有手动注入所有服务,而是创建了一个 class 来提供服务,例如,它会注入服务。这个 class 然后被注入派生的 classes 并传递给基础 class.

派生 class:

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

基础class:

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

提供服务class:

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

如果父 class 是从第 3 方插件获取的(并且您不能更改源),您可以这样做:

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. 您的顾虑完全与组件继承有关。最有可能的是,您解决这个问题的原因是因为您需要在每个派生的 class 中重复大量的 non-dry(WET?)代码。如果您想为所有组件 和服务 使用单一入口点,则需要执行额外的步骤。

  2. 每个组件都扩展了 BaseComponent

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

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

丑陋的骇客

前段时间,我的一些客户想加入两个 BIG angular 项目到昨天(angular v4 到 angular v8)。项目 v4 对每个组件使用 BaseView class,它包含 tr(key) 翻译方法(在 v8 中我使用 ng-translate)。因此,为了避免切换翻译系统和编辑数百个模板(在 v4 中)或并行设置 2 个翻译系统,我使用了以下丑陋的 hack(我并不以此为荣)- 在 AppModule class 我添加以下构造函数:

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);
  }
}