Внедрение наследования и зависимостей


101

У меня есть набор компонентов 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экземпляр.
Günter Zöchbauer

Вы совершенно правы, мой код будет генерировать множество экземпляров myService. Нашел решение, которое позволяет избежать этого, но добавляет больше кода в производные классы ...
maxhb

Внедрение инжектора - это улучшение только тогда, когда есть несколько различных услуг, которые необходимо ввести во многих местах. Вы также можете внедрить службу, которая имеет зависимости от других служб, и предоставить их с помощью получателя (или метода). Таким образом, вам нужно внедрить только одну службу, но вы можете использовать набор служб. Ваше решение и предлагаемая мной альтернатива имеют тот недостаток, что затрудняют определение того, какой класс зависит от того, какой сервис. Я бы предпочел создавать инструменты (например, живые шаблоны в WebStorm), которые упрощают создание шаблонного кода и четко
указывают

Ответы:


73

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

Это не абсурд. Так работают конструкторы и внедрение конструкторов.

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

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

Он скрывает зависимости, что затрудняет чтение кода.
Это нарушает ожидания человека, знакомого с тем, как работает Angular2 DI.
Он прерывает автономную компиляцию, которая генерирует статический код для замены декларативного и императивного DI для повышения производительности и уменьшения размера кода.


6
Чтобы было понятно: мне это нужно ВЕЗДЕ. Попытка переместить эту зависимость в мой суперкласс, чтобы КАЖДЫЙ производный класс мог получить доступ к службе без необходимости вводить ее индивидуально в каждый производный класс.
maxhb

9
Ответ на его собственный вопрос - уродливый взлом. Вопрос уже показывает, как это нужно делать. Я уточнил немного больше.
Günter Zöchbauer

7
Это правильный ответ. OP ответил на свой вопрос, но при этом нарушил множество соглашений. То, что вы перечислили реальные недостатки, тоже полезно, и я ручаюсь за это - я думал о том же.
dudewad

6
Я действительно хочу (и продолжаю) использовать этот ответ вместо «взлома» OP. Но я должен сказать, что это кажется далеким от СУХОГО и очень болезненно, когда я хочу добавить зависимость в базовый класс. Мне просто пришлось добавить ctor-инъекции (и соответствующие superвызовы) примерно к 20+ классам, и это число будет только расти в будущем. Итак, две вещи: 1) я бы не хотел, чтобы «большая кодовая база» сделала это; и 2) Слава Богу за vim qи vscodectrl+.

6
То, что это неудобно, не означает, что это плохая практика. Конструкторы неудобны, потому что очень сложно добиться надежной инициализации объекта. Я бы сказал, что худшая практика - это создание сервиса, которому нужен «базовый класс, внедряющий 15 сервисов и наследуемый 6».
Günter Zöchbauer 02

66

Обновленное решение предотвращает создание нескольких экземпляров 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.oughttram.io/angular/2015/05/18/dependency-injection-in-angular- 2.html


7
Это затрудняет понимание того, какие сервисы на самом деле внедряются.
Саймон Дюфур

1
Разве это не должно быть и this.myServiceA = injector.get(MyServiceA);т. Д.?
jenson-button-event

10
Ответ @Gunter Zochbauer правильный. Это неправильный способ сделать это, и он нарушает многие угловые соглашения. Было бы проще, если бы кодирование всех этих вызовов инъекций было «болью», но если вы хотите пожертвовать необходимостью написания кода конструктора, чтобы иметь возможность поддерживать большую кодовую базу, тогда вы стреляете себе в ногу. Это решение не масштабируется, ИМО, и в будущем вызовет множество сбивающих с толку ошибок.
dudewad

3
Нет риска появления нескольких экземпляров одной и той же службы. Вам просто нужно предоставить службу в корне вашего приложения, чтобы предотвратить множественные экземпляры, которые могут возникнуть в разных ветвях приложения. Передача сервисов при изменении наследования не создает новых экземпляров классов. Ответ @Gunter Zochbauer правильный.
ktamlyn

@maxhb изучали ли вы когда-нибудь Injectorглобальное расширение, чтобы не связывать какие-либо параметры AbstractComponent? fwiw, я думаю, что свойство, вводящее зависимости в широко используемый базовый класс, чтобы избежать беспорядочного связывания конструкторов, является совершенно допустимым исключением из обычного правила.
quentin-starin

5

Вместо того, чтобы вводить все сервисы вручную, я создал класс, предоставляющий сервисы, например, он вводит сервисы. Затем этот класс вводится в производные классы и передается базовому классу.

Производный класс:

@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
Проблема здесь в том, что вы рискуете создать службу «ящика нежелательной почты», которая по сути является просто прокси для службы инжектора.
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;
    }
}

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

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

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

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


2
Тогда базовый класс будет зависеть от всех сервисов, которые нужны его дочерним элементам. ChildComponentA нужен ServiceA? Что ж, теперь ChildComponentB также получает ServiceA.
knallfrosch

1

Насколько я понимаю, чтобы унаследовать от базового класса, вам сначала нужно создать его экземпляр. Чтобы создать его экземпляр, вам необходимо передать необходимые параметры его конструктору, таким образом, вы передаете их от дочернего элемента к родительскому через вызов super (), так что это имеет смысл. Инжектор, конечно, еще одно жизнеспособное решение.


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

Уродливый взлом

Некоторое время назад некоторые из моих клиентов хотят присоединиться к двум БОЛЬШИМ проектам angular ко вчерашнему дню (angular v4 в angular v8). Project v4 использует класс BaseView для каждого компонента и содержит tr(key)метод перевода (в v8 я использую ng-translate). Поэтому, чтобы избежать переключения системы переводов и редактирования сотен шаблонов (в v4) или настройки двух систем перевода параллельно, я использую следующий уродливый хак (я не горжусь этим) - в 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);
  }
}

да согласен, это некрасиво,
CMS
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.