继承和依赖注入


97

我有一组angular2组件,都应该注入一些服务。我首先想到的是,最好是创建一个超类并在其中注入服务。然后,我的任何组件都将扩展该超类,但是这种方法不起作用。

简化示例:

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()调用来解决此问题,但这显然是荒谬的。

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


这不是重复项。所参考的问题是关于如何构造一个DERIVED类,该类可以访问已经定义的超类注入的服务。我的问题是关于如何构造将服务继承到派生类的SUPER类。只是相反。
maxhb

您的回答(问题中的内联)对我而言没有意义。通过这种方式,您可以创建与应用程序Angular所使用的喷射器无关的喷射器。使用new MyService()而不是注入可以为您提供完全相同的结果(效率更高的除外)。如果要跨不同的服务和/或组件共享同一服务实例,则将无法使用。每个类将获得另一个MyService实例。
君特Zöchbauer

您完全正确,我的代码将生成许多的实例myService。找到了避免这种情况的解决方案,但向派生类添加了更多代码...
maxhb

只有在许多地方需要注入几种不同的服务时,才可以注入注入器。您还可以注入与其他服务具有依赖性的服务,并使用getter(或方法)提供它们。这样,您只需要注入一个服务即可使用一组服务。您的解决方案和我提出的替代方案都有两个缺点,即它们使查看哪个类取决于什么服务变得更加困难。我宁愿创建工具(如在WebStorm现场模板),使人们更容易地创建样板代码并明确说明的依赖
君特Zöchbauer

Answers:


72

我可以通过在每个组件中注入MyService并将该参数用于super()调用来解决此问题,但这绝对是荒谬的。

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

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

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

它隐藏了依赖关系,使代码更难阅读。
它违反了熟悉Angular2 DI的工作原理的期望。
它破坏了生成静态代码以替换声明性和命令性DI的脱机编译,从而提高了性能并减小了代码大小。


4
为了清楚起见:我在任何地方都需要它。尝试将该依赖关系移至我的超类,以便EACH派生类可以访问服务,而无需将其分别注入每个派生类。
maxhb '16

9
他自己的问题的答案是丑陋的。这个问题已经说明了应该如何做。我详细说明了一些。
君特Zöchbauer

7
这个答案是正确的。OP回答了他们自己的问题,但这样做违反了许多约定。您列出了实际的缺点也很有帮助,我会保证-我在想同样的事情。
dudewad

6
我真的想(并继续)在OP的“ hack”上使用此答案。但是我不得不说,这似乎与DRY相去甚远,并且当我想在基类中添加依赖项时非常痛苦。我只需要将ctor注入(和相应的super调用)添加到大约20多个类中,并且这个数字只会在将来增加。所以有两件事:1)我不希望看到一个“大型代码库”来做到这一点;和2)感谢上帝的vim q和vscodectrl+.

5
仅仅因为不方便并不意味着它是不好的做法。构造函数很不方便,因为很难可靠地完成对象初始化。我认为较差的做法是构建需要“注入15个服务并被6继承的基类”的服务。
君特Zöchbauer

64

更新的解决方案,防止使用全局注入器生成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ünterZöchbauer的评论):

  • 只有在许多地方需要注入几种不同的服务时,才注入全局注入器。如果您只有一项共享服务,那么最好在派生类中注入该服务。
  • 我的解决方案和他提出的替代方案都有两个缺点,即它们使查看哪个类取决于什么服务变得更加困难。

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


7
但是,这使得很难理解实际上注入了哪些服务。
西蒙·杜福

不应该这样this.myServiceA = injector.get(MyServiceA);吗?
jenson-button-event

9
@Gunter Zochbauer的答案是正确的。这不是执行此操作的正确方法,并且打破了许多角度惯例。编码所有这些注入调用可能是“痛苦”的,这可能会更简单,但是如果您要牺牲不得不编写构造函数代码以维护大型代码库的能力,那么您将无所适从。IMO无法解决此解决方案,并且会在将来引起许多令人困惑的错误。
dudewad

3
不会存在同一服务的多个实例的风险。您只需在应用程序的根目录处提供服务,以防止可能在应用程序的不同分支上发生多个实例。将服务传递给继承更改不会创建类的新实例。@Gunter Zochbauer的答案是正确的。
ktamlyn

@maxhb您是否曾经探索过Injector全局扩展以避免避免将任何参数链接到AbstractComponent?首先,我认为将依赖项注入到广泛使用的基类中以避免混乱的构造函数链接是通常规则的完全有效的例外。
quentin-starin 18/09/17

4

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

派生类:

@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) {
    }
}

3
这里的问题是您冒着创建“垃圾抽屉”服务的风险,该服务实际上只是Injector服务的代理。
kpup

1

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

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. 您的关注与组件继承完全相关。您最有可能落在这个问题上的原因是因为您需要在每个派生类中重复大量的非干燥(WET?)代码。如果您希望所有组件和服务都受益于单个入口点,则需要执行额外的步骤。

  2. 每个组成部分扩展了 BaseComponent

如果决定使用派生类的构造函数,则还有一个缺点,因为您将需要调用super()并传入所有依赖项。尽管我并没有真正看到需要使用constructor代替代替的用例ngOnInit,但完全有可能存在这种用例。


2
然后,基类将依赖于其子级需要的所有服务。ChildComponentA需要ServiceA吗?现在,ChildComponentB也获得了ServiceA。
knallfrosch

0

如果父类是从第三方插件获得的(并且您不能更改源代码),则可以执行以下操作:

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

0

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


0

丑陋的哈克

前段时间,我的一些客户希望将两个BIG角度项目加入到昨天(角度v4到角度v8)。Project v4对每个组件使用BaseView类,并且包含tr(key)翻译方法(在v8中,我使用ng-translate)。因此,为了避免切换翻译系统并并行编辑数百个模板(在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);
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.