web-dev-qa-db-ja.com

Angular 2:サービスをクラスに注入する

angular形状を表すクラスがあります。コンストラクターを使用して、そのクラスの複数のインスタンスをインスタンス化できるようにしたいです。

コンストラクターは、その形状のプロパティを表す複数の引数を取ります。

constructor(public center: Point, public radius: number, fillColor: string,
    fillOpacity: number, strokeColor: string, strokeOpacity: number, zIndex: number)

クラス内では、地図上に図形を描画する機能を提供するサービスを使用します。そのサービスをクラスにインジェクトし、コンストラクタを標準的な方法で使用することは可能ですか?.

だから私は以下のようなことをして、Angular自動的に注入された依存関係を解決します.

constructor(public center: GeoPoint, public radius: number, 
    fillColor: string, fillOpacity: number, strokeColor: string, strokeOpacity: number, 
    zIndex: number, @Inject(DrawingService) drawingService: DrawingService)
26
Liplattaa

私は問題を解決することができました。

Angular 2-4は、コンストラクターパラメーターの外部に依存関係を注入できる反射型インジェクターを提供します。

@angular/coreからReflectiveインジェクターをインポートするだけでした。

import {ReflectiveInjector} from '@angular/core';

その後:

let injector = ReflectiveInjector.resolveAndCreate([DrawingService]);
this.drawingApi = injector.get(DrawingService);

クラスは@Injectableデコレータで装飾する必要さえありません。唯一の問題は、DrawingServiceのすべての依存関係と、ネストされたすべての依存関係を提供する必要があるため、維持するのが難しいことです。

[〜#〜] edit [〜#〜]

角度5

import { Injector } from "@angular/core";

const injector = Injector.create([
    { provide: DrawingService }
]);
this.drawingApi = injector.get(DrawingService);

角度6

import { Injector } from "@angular/core";

const injector = Injector.create({ 
  providers: [ 
    { provide: DrawingService },
  ]
});
this.drawingApi = injector.get(DrawingService);
37
Liplattaa

望ましいまたは非常に類似した結果を達成するための2つの可能な方法を次に示します。

最初のアプローチ-エンティティまたは非サービスオブジェクトにマネージャーを使用する

オブジェクトのインスタンス化を担当する1つ以上のファクトリサービスがあります。

これは、itがオブジェクトに必要な依存関係をさらに提供し、それらを自分で渡す必要がないことを意味します。

たとえば、エンティティがクラス階層として存在するとします。

abstract class Entity { }

class SomeEntity extends Entity { 
   ...
}

その後、サービスであり、エンティティを構築できるEntityManagerを持つことができます。

@Injectable()   // is a normal service, so DI is standard
class EntityManager {

  constructor(public http: Http) { }    // you can inject any services now

  create<E extends Entity>(entityType: { new(): E; }): E {
    const entity = new entityType();    // create a new object of that type
    entity.manager = this;              // set itself on the object so that that object can access the injected services like http - one can also just pass the services not the manager itself
    return entity;
  }

}

必要に応じて、構築パラメーターを使用することもできます(ただし、createはすべてのタイプのエンティティを処理する必要があるため、タイプ情報はありません):

class SomeEntity extends Entity { 
   constructor(param1, param1) { ... }
}

// in EntityManager
create<E extends Entity>(entityType: { new(): E; }, ...params): E {
    const entity = new entityType(...params);
    ...
}

エンティティはマネージャーを宣言できます:

abstract class Entity {
  manager: EntityManager;
}

そして、あなたのエンティティはそれを使って何でもできます:

class SomeEntity extends Entity {
  doSomething() {
    this.manager.http.request('...');
  }
}

エンティティ/オブジェクトを作成する必要があるたびに、このマネージャーを使用します。 EntityManager自体を挿入する必要がありますが、エンティティはフリーオブジェクトです。しかし、すべてのangularコードはコントローラーまたはサービスなどから開始されるため、マネージャーを注入することが可能になります。

// service, controller, pipe, or any other angular-world code

constructor(private entityManager: EntityManager) {
    this.entity = entityManager.create(SomeEntity);
}

このアプローチは、任意のオブジェクトに適応させることもできます。クラス階層は必要ありませんが、TypeScriptを使用する方がうまく機能します。また、特にドメイン/オブジェクト指向のアプローチでは、この古い方法でコードを再利用できるため、オブジェクトの基本クラスを用意することも理にかなっています。

[〜#〜] pros [〜#〜]:このアプローチは、完全なDI階層に存在し、望ましくない副作用が少ないため、より安全です。

[〜#〜] cons [〜#〜]:欠点は、newを二度と使用できないこと、および任意のコードでこれらのサービスにアクセスできないことです。あなたは常にDIとあなたの工場/工場に頼る必要があります。

2番目のアプローチ-h4ckz0rs

オブジェクトで必要なサービスを(DIを介して)取得するための専用のサービスを作成します。

この部分は最初のアプローチと非常によく似ていますが、このサービスはファクトリではありません。代わりに、注入されたサービスを別のファイルでこのクラスの外部で定義されたオブジェクトに渡します(後の説明)。例えば:

...
import { externalServices } from './external-services';

@Injectable()
export class ExternalServicesService {

  constructor(http: Http, router: Router, someService: SomeService, ...) {
    externalServices.http = http;
    externalServices.router = router;
    externalServices.someService = someService;
  }

}

サービスを保持するオブジェクトは、独自のファイルで次のように定義されます。

export const externalServices: {
  http,
  router,
  someService
} = { } as any;

サービスはタイプ情報を使用していないことに注意してください(これは欠点ですが、必要です)。

次に、ExternalServicesServiceが1回挿入されることを確認する必要があります。最適な場所は、メインアプリコンポーネントを使用することです。

export class AppComponent {

  constructor(..., externalServicesService: ExternalServicesService) {

最後に、メインのアプリコンポーネントがインスタンス化された後、いつでも任意のオブジェクトでサービスを使用できます。

import { externalServices } from '../common/externalServices' // or wherever is defined

export class SomeObject() {
    doSomething() {
        externalServices.http().request(...) // note this will be called after ng2 app is ready for sure
    }
}

クラスコードまたはアプリのインスタンス化後にインスタンス化されていないオブジェクトでは、これらのサービスを呼び出すことはできません。しかし、典型的なアプリでは、これは決して必要ではありません。

今、この奇妙な設定に関するいくつかの説明:

同じファイルの代わりに別のファイルでオブジェクトexternalServicesを使用する理由、または単にクラス自体にサービスを(静的属性として)保存するのはなぜですか?

その理由は、コードを拡張しているときです。 --prodモードのangular-cli/webpackを介して、正しく解決できない循環依存関係を取得する可能性が非常に高く、見つけるのが難しいerrorsいエラーが発生します-私はすでにこれを経験しています:).

フォームのエラー

未定義のプロパティ「プロトタイプ」を読み取ることができません

--prodフラグを指定して実行する場合にのみ表示され、依存関係が正しく解決されないという事実を示唆します。

ExternalServicesServiceexternalServicesのみに依存してサービスインスタンスを渡し、アプリケーションがExternalServicesServiceを一度だけ(たとえば、メインAppComponentに)注入し、その後すべてがコード/オブジェクトは、externalServicesのみを使用してサービスを取得します。

したがって、そのようなコードは、それ以上の依存関係がないexternalServicesをインポートするだけで済みます(サービスも入力されていないため)。 ExternalServicesServiceをインポートする場合、他のすべてをインポートし、双方向の依存関係を静的に解決することはできませんでした。そして、これはng2/webpackでprodにバンドルすると大きな問題になります。

importsが必要になるため、サービスに型を使用する場合も同じことが起こります。

[〜#〜] pros [〜#〜]:このアプローチは、セットアップが完了すると簡単に使用でき、newを自由に使用できます。基本的に、どのコードファイルでもexternalServicesをインポートでき、この方法で公開したいサービスにすぐにアクセスできます。

CONS:マイナス面は、ハック的なセットアップと、周期的な依存によって引き起こされる可能性のある問題です。 externalServicesがこれらのサービスをすぐに使用できるかどうかわからないので、より敏感です。これらは、ng2アプリが起動し、ExternalServicesServiceが最初に挿入された後にのみ定義されます。欠点は、それらのサービスのタイプ情報がもうないことです。


PS:このトピックがそれほど人気が​​ない理由はわかりません。

たとえば、ドメイン指向設計のファンであること、強力なエンティティ(たとえば、REST呼び出しまたは他のサービスとのやり取り)に向けられたメソッドを使用すること)が重要であり、この制限により常に困難になりました。

アンギュラjsとAngular2 +の両方でこの制限を克服する必要がありましたが、それはまだライブラリで対処されていないようです。

16
Ovidiu Dolha

Angular 5.x:

import { Injector } from "@angular/core";
export class Model {

    static api: Api;

    constructor(data: any) {

        // check the api ref not exist
        // We don't want to initiate a new object every time
        if (!Model.api){
            //try inject my api service which use the HttpClient
            const injector: any = Injector.create([{ provide: Api, useClass: Api, deps: [] }]);
            Model.api = injector.get(Api);
        }

        // .....

    }
}
5
Eymen Elkum

GitHubにvojtajinaからの投稿があり、この問題に対する素晴らしいアプローチを提供しています。この答えは単なるリンクですが、他の興味深い情報がそこにあるので、コンテキストでこれを読むことは本当に良いです:

https://github.com/angular/di.js/issues/22#issuecomment-3677334

0
ndvo