Наследование и инъекция зависимостей


у меня есть набор компонентов 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() звонок, но это определенно какой-то абсурд.

Как правильно организовать мои компоненты, чтобы они унаследовали службу от суперкласса?

5   51  

5 ответов:

я мог бы решить это, введя MyService в каждый компонент и использовать этот аргумент для вызова super (), но это определенно какой-то абсурд.

это не абсурд. Вот как работают конструкторы и инъекция конструктора.

каждый вводимый класс должен объявить зависимости в качестве параметров конструктора, и если суперкласс также имеет зависимости, они должны быть перечислены в конструкторе подкласса и переданы вместе с суперклассом с super(dep1, dep2) звонок.

передача Вокруг инжектора и получение зависимостей императивно имеет серьезные недостатки.

он скрывает зависимости, что делает код труднее читать.
Это нарушает ожидания одного знакомого с тем, как работает 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 без необходимости вводить MyService в каждый производный класс.

есть некоторые минусы этого решения (см. Ccomment от @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
    }
}

Я бы пропустил этот дополнительный шаг и просто добавил inject все службы в BaseComponent, например:

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

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

этот метод предполагает 2 вещи:

  1. ваша забота полностью связана с наследованием компонентов. Скорее всего, причина, по которой вы приземлились на этот вопрос, заключается в подавляющем количестве несухих (влажных?) код нужно повторить в каждом производном классе. Если вы хотите преимущества одной точки входа для всех ваших компонентов и услуги, вам нужно будет сделать дополнительный шаг.

  2. каждый компонент расширяет BaseComponent

существует также недостаток, если вы решите использовать конструктор производного класса, так как вам нужно будет вызвать super() и передать все зависимости. Хотя я действительно не вижу прецедента, который требует использование constructor вместо ngOnInit, вполне возможно, что такой вариант использования не существует.