Наследование и зависимость
У меня есть набор 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()
, но это определенно какой-то абсурд.
Как правильно упорядочить мои компоненты так, чтобы они наследовали сервис из суперкласса?
Ответы
Ответ 1
Обновленное решение, предотвращает создание нескольких экземпляров 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
Ответ 2
Я мог бы решить эту проблему, внедрив MyService в каждый компонент и использовать этот аргумент для вызова super(), но это определенно какой-то абсурд.
Это не абсурдно. Так работает конструктор и инжекция конструктора.
Каждый инъецируемый класс должен объявлять зависимости как параметры конструктора, и если у суперкласса также есть зависимости, они также должны быть перечислены в конструкторе подкласса и переданы в суперкласс с super(dep1, dep2)
.
Обход инжектора и получение зависимостей обязательно имеет серьезные недостатки.
Он скрывает зависимости, которые затрудняют чтение кода.
Это противоречит ожиданиям тех, кто знаком с тем, как работает Angular2 DI.
Это нарушает автономную компиляцию, которая генерирует статический код, чтобы заменить декларативный и императивный DI для повышения производительности и уменьшения размера кода.
Ответ 3
Вместо того, чтобы вводить все службы вручную, я создал класс, предоставляющий услуги, например, он получает введенные услуги. Затем этот класс вводится в производные классы и передается базовому классу.
Производный класс:
@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) {
}
}
Ответ 4
Вместо внедрения службы, которая имеет все другие службы в качестве зависимостей, вот так:
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;
}
}
Эта техника предполагает 2 вещи:
-
Ваша забота полностью связана с наследованием компонентов. Скорее всего, причина, по которой вы попали на этот вопрос, заключается в том, что огромное количество несухого (WET?) Кода необходимо повторять в каждом производном классе. Если вы хотите воспользоваться преимуществами единой точки входа для всех ваших компонентов и услуг, вам потребуется выполнить дополнительный шаг.
-
Каждый компонент расширяет BaseComponent
Есть также недостаток, если вы решите использовать конструктор производного класса, так как вам нужно будет вызвать super()
и передать все зависимости. Хотя я на самом деле не вижу ngOnInit
использования, который требует использования constructor
вместо ngOnInit
, вполне возможно, что такой вариант использования существует.
Ответ 5
Если родительский класс был получен из стороннего плагина (и вы не можете изменить источник), вы можете сделать это:
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);
}
}