継承と依存性注入


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 2016

あなたの答え(質問のインライン)は私には意味がありません。このようにして、アプリケーションでAngularが使用するインジェクターから独立したインジェクターを作成します。インジェクションのnew MyService()代わりに使用すると、まったく同じ結果が得られます(より効率的です)。異なるサービスやコンポーネント間で同じサービスインスタンスを共有する場合、これは機能しません。すべてのクラスは別のMyServiceインスタンスを取得します。
ギュンターZöchbauer

あなたは完全に正しいです、私のコードはたくさんのインスタンスを生成しますmyService。これを回避し、派生クラスにさらにコードを追加するソリューションを見つけました...
maxhb

インジェクターの注入は、多くの場所に注入する必要のあるいくつかの異なるサービスがある場合にのみ改善されます。他のサービスに依存するサービスを注入し、ゲッター(またはメソッド)を使用してそれらを提供することもできます。この方法では、1つのサービスを挿入するだけで、一連のサービスを使用できます。あなたの解決策と私の提案する代替案には、どのクラスがどのサービスに依存しているかを確認するのが難しくなるという両方の欠点があります。私はむしろ定型的なコードを作成し、依存関係について明示的にすることが容易になります(WebStormでのライブのテンプレートのような)ツールを作成したい
ギュンターZöchbauer

回答:


72

MyServiceをすべてのコンポーネント内に注入し、super()呼び出しにその引数を使用することでこれを解決できますが、それは明らかに不合理なことです。

それはばかげていません。これが、コンストラクターとコンストラクターインジェクションの仕組みです。

すべての注入可能なクラスは、依存関係をコンストラクターパラメーターとして宣言する必要があります。スーパークラスにも依存関係がある場合、これらもサブクラスのコンストラクターにリストし、スーパークラスに渡す必要があります。 super(dep1, dep2)呼び出しでます。

インジェクターを回避して依存関係を取得することは、重大な欠点があります。

依存関係を隠し、コードを読みにくくします。
これは、Angular2 DIがどのように機能するかをよく知っている人の期待に違反します。
静的コードを生成して宣言型および命令型DIを置き換えるオフラインコンパイルを中断し、パフォーマンスを向上させてコードサイズを削減します。


4
明確にするために:どこでも必要です。その依存関係をスーパークラスに移動して、各派生クラスが各派生クラスに個別に注入する必要なくサービスにアクセスできるようにします。
maxhb

9
彼自身の質問への答えは醜いハックです。質問はすでにそれがどのように行われるべきかを示しています。もう少し詳しく説明しました。
ギュンターZöchbauer

7
この答えは正しいです。OPは彼ら自身の質問に答えましたが、そうすることで多くの慣習を破りました。実際の欠点をリストアップしたことも役に立ち、私はそれを保証します-私は同じことを考えていました。
dudewad

6
私はこの答えをOPの「ハック」に対して本当に使いたい(そして続けたい)。しかし、基本クラスに依存関係を追加したい場合、これはDRYとはかけ離れており、非常に苦痛であると言わざるを得ません。super約20以上のクラスにctorインジェクション(および対応する呼び出し)を追加するだけで、その数は将来的に増加するだけです。したがって、2つのことを行います。1)「大規模なコードベース」がこれを実行するのを見たくありません。2)vim qとvscode について神に感謝しますctrl+.

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のCcommentを参照):

  • グローバルインジェクターの注入は、多くの場所に注入する必要があるいくつかの異なるサービスがある場合にのみ改善されます。共有サービスが1つしかない場合は、そのサービスを派生クラス内に挿入する方がおそらくより簡単です。
  • 私のソリューションと彼が提案した代替案には、どのクラスがどのサービスに依存しているかを確認するのが難しくなるという欠点があります。

Angular2での依存性注入の非常によく書かれた説明については、問題を解決するのに大いに役立ったこのブログ投稿を参照してください:http : //blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular- 2.html


7
このため、実際に挿入されているサービスを理解することはかなり困難です。
Simon Dufour 2017年

それはそうではありませんthis.myServiceA = injector.get(MyServiceA);か?
jenson-button-event 2017

9
@Gunter Zochbauerの答えは正しいものです。これはこれを行う正しい方法ではなく、多くの角度の慣習を破ります。これらすべてのインジェクション呼び出しのコーディングは「苦痛」であるという点でより簡単かもしれませんが、大きなコードベースを維持できるようにするためにコンストラクターコードを記述する必要を犠牲にしたい場合は、足を踏みにじることになります。この解決策はスケーラブルではなく、IMOであり、今後混乱を招く多くのバグを引き起こします。
dudewad

3
同じサービスの複数のインスタンスのリスクはありません。アプリケーションの異なるブランチで発生する可能性のある複数のインスタンスを防ぐために、アプリケーションのルートでサービスを提供するだけです。継承の変更にサービスを渡しても、クラスの新しいインスタンスは作成されません。@Gunter Zochbauerの答えは正しいです。
ktamlyn

@maxhb Injectorパラメータをチェーンする必要がないように、グローバルに拡張することを検討したことがありますAbstractComponentか?fwiw、乱雑なコンストラクタチェーンを回避するために、広く使用されている基本クラスに依存関係を注入するプロパティは、通常のルールの完全に有効な例外だと思います。
クエンティンスターリン2018

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
ここでの問題は、本質的にインジェクターサービスの単なるプロキシである「ジャンクドロワー」サービスを作成するリスクがあることです。
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));
  }
}

または最も良い方法(コンストラクターのパラメーターは1つだけにしてください):

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

醜いハック

少し前に、私のクライアントの一部が昨日まで2つの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);
  }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.