Ін'єкція успадкування та залежності


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екземпляр.
Günter Zöchbauer

Ви повністю праві, мій код створить безліч екземплярів myService. Знайшов рішення, яке уникає цього, але додає більше коду до похідних класів ...
maxhb

Ін’єкція ін’єктора - це лише поліпшення, коли існує кілька різних служб, які потрібно вводити в багатьох місцях. Ви також можете внести послугу, яка має залежності, до інших служб і надати їх за допомогою геттера (або методу). Таким чином вам потрібно ввести лише одну послугу, але ви можете скористатися набором послуг. Твоє рішення та запропонована мною альтернатива мають і той недолік, що ускладнюють розуміння того, який клас залежить від якої послуги. Я вважаю за краще створити інструменти (наприклад, шаблони в реальному часі в WebStorm), які полегшують створення зразкового коду та чітке висловлення залежностей
Гюнтер Цохбауер,

Відповіді:


72

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

Це не абсурдно. Ось як працюють конструктори та вприскування конструктора.

Кожен ін'єкційний клас повинен оголосити залежності як параметри конструктора, і якщо суперклас також має залежності, вони також повинні бути перелічені в конструкторі підкласу та передані суперкласу із super(dep1, dep2)викликом.

Проходження інжектора та набуття залежностей імперативно має серйозні недоліки.

Він приховує залежності, що ускладнює читання коду.
Це порушує очікування знайомого з тим, як працює Angular2 DI.
Він порушує офлайн-компіляцію, яка генерує статичний код для заміни декларативного та імперативного DI для покращення продуктивності та зменшення розміру коду.


4
Тільки щоб було зрозуміло: мені це потрібно СКРІЗЬ. Намагаюся перенести цю залежність у мій супер клас, щоб КОЖНИЙ похідний клас міг отримати доступ до служби без необхідності вводити її окремо до кожного похідного класу.
maxhb

9
Відповідь на його власне запитання - потворний хак. Питання вже демонструє, як це слід робити. Я трохи детальніше розробив.
Günter Zöchbauer

7
Ця відповідь правильна. ОП відповіли на власне запитання, але цим порушили багато умов. Те, що ви перелічили фактичні недоліки, також корисно, і я поручуся за це - я думав про те саме.
dudewad

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

5
Те, що це незручно, не означає, що це погана практика. Конструктори незручні, оскільки дуже важко домогтися надійної ініціалізації об’єкта. Я стверджую, що гіршою практикою є побудова служби, яка потребує "базового класу, що вводить 15 служб і успадковується 6".
Günter 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 у кожен похідний клас.

У цього рішення є кілька мінусів (див. Ccomment від @ Günter Zö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? fwiw, я думаю, що властивість, що вводить залежності в широко використовуваний базовий клас, щоб уникнути брудної ланцюжка конструкторів, є цілком виправданим винятком із звичайного правила.
квентин-старін,

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

Ця техніка передбачає 2 речі:

  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

ПОРТИЙ ЗЛОМ

Деякий час тому хтось із моїх клієнтів хоче приєднати два великі кутові проекти до вчорашнього дня (angular v4 into angular v8). Проект 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);
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.