詳細ガイド
依存性の注入

依存関係プロバイダーの設定

前のセクションでは、クラスインスタンスを依存関係として使用する仕組みについて説明しました。 クラスに加えて、booleanstringDate、オブジェクトなどの値を依存関係として使用できます。 Angularは、依存関係の設定を柔軟にするために必要なAPIを提供しており、これらの値をDIで利用可能にできます。

プロバイダートークンの指定

プロバイダートークンとしてサービスクラスを指定した場合、インジェクターはデフォルトでnew演算子を使用してそのクラスをインスタンス化します。

次の例では、アプリケーションコンポーネントはLoggerインスタンスを提供します。

src/app/app.component.ts

      
providers: [Logger],

ただし、DIを構成して、Loggerプロバイダートークンを別のクラスまたはその他の値に関連付けることができます。 そのため、Loggerが注入されると、構成された値が代わりに使用されます。

実際、クラスプロバイダーの構文は、Providerインターフェースで定義されたプロバイダー構成に展開される省略記号です。 Angularは、この場合のproviders値を、次のように完全なプロバイダーオブジェクトに展開します。

src/app/app.component.ts

      
[{ provide: Logger, useClass: Logger }]

展開されたプロバイダー構成は、2つのプロパティを持つオブジェクトリテラルです。

  • provideプロパティは、依存関係値を消費するためのキーとして機能するトークンを保持します。
  • 2番目のプロパティはプロバイダー定義オブジェクトで、インジェクターが依存関係値をどのように作成するかを指示します。プロバイダー定義は、次のいずれかになります。
    • useClass - このオプションは、依存関係が注入されるときに、Angular DIが提供されたクラスをインスタンス化することを指示します。
    • useExisting - トークンにエイリアスを付けて、既存のトークンを参照できます。
    • useFactory - 依存関係を作成する関数を定義できます。
    • useValue - 依存関係として使用する静的な値を提供します。

以下のセクションでは、さまざまなプロバイダー定義の使用方法について説明します。

クラスプロバイダー: useClass

useClassプロバイダーキーを使用すると、指定されたクラスの新しいインスタンスを作成して返します。

このタイプのプロバイダーを使用して、共通またはデフォルトのクラスの代替実装を置き換えることができます。 代替実装はたとえば異なる戦略を実装したり、デフォルトクラスを拡張したり、テストケースで実際のクラスの動作をエミュレートしたりできます。

次の例では、Logger依存関係がコンポーネントまたはその他のクラスで要求されると、BetterLoggerがインスタンス化されます。

src/app/app.component.ts

      
[{ provide: Logger, useClass: BetterLogger }]

代替クラスプロバイダーが独自の依存関係を持つ場合は、親モジュールまたはコンポーネントのprovidersメタデータプロパティに両方のプロバイダーを指定します。

src/app/app.component.ts

      
[  UserService, // `EvenBetterLogger`に必要な依存関係。  { provide: Logger, useClass: EvenBetterLogger },]

この例では、EvenBetterLoggerはログメッセージにユーザー名を表示します。このロガーは、注入されたUserServiceインスタンスからユーザーを取得します。

src/app/even-better-logger.component.ts

      
@Injectable()export class EvenBetterLogger extends Logger {  constructor(private userService: UserService) {}  override log(message: string) {    const name = this.userService.user.name;    super.log(`Message to ${name}: ${message}`);  }}

Angular DIは、UserService依存関係を構成する方法を知っています。なぜなら、UserServiceは上記で構成されており、インジェクターで利用可能だからです。

エイリアスプロバイダー: useExisting

useExistingプロバイダーキーを使用すると、1つのトークンを別のトークンにマッピングできます。 つまり、最初のトークンは2番目のトークンに関連付けられたサービスのエイリアスになり、同じサービスオブジェクトにアクセスする2つの方法が作成されます。

次の例では、コンポーネントが新しいロガーまたは古いロガーのいずれかを要求すると、インジェクターはNewLoggerのシングルトンインスタンスを注入します。 このように、OldLoggerNewLoggerのエイリアスです。

src/app/app.component.ts

      
[  NewLogger,  // NewLoggerへの参照でOldLoggerにエイリアスを付ける  { provide: OldLogger, useExisting: NewLogger},]

注: useClassを使用してOldLoggerNewLoggerをエイリアス付けしないようにしてください。これは、2つの異なるNewLoggerインスタンスが作成されるためです。

ファクトリプロバイダー: useFactory

useFactoryプロバイダーキーを使用すると、ファクトリ関数を呼び出すことで依存関係オブジェクトを作成できます。 このアプローチでは、DIとアプリケーションの他の場所で利用可能な情報に基づいて、動的な値を作成できます。

次の例では、承認されたユーザーのみがHeroServiceで秘密のヒーローを表示できます。 承認は、別のユーザーがログインした場合など、単一のアプリケーションセッション中に変更される可能性があります。

UserService内のセキュリティに敏感な情報をHeroServiceから分離するには、HeroServiceコンストラクターに、秘密のヒーローの表示を制御するためのブール型のフラグを与えます。

src/app/heroes/hero.service.ts

      
class HeroService {  constructor(    private logger: Logger,    private isAuthorized: boolean) { }  getHeroes() {    const auth = this.isAuthorized ? 'authorized' : 'unauthorized';    this.logger.log(`Getting heroes for ${auth} user.`);    return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);  }}

isAuthorizedフラグを実装するには、ファクトリプロバイダーを使用して、HeroServiceの新しいロガーインスタンスを作成します。 これは、ヒーローサービスを構築する際にLoggerを手動で渡す必要があるためです。

src/app/heroes/hero.service.provider.ts

      
const heroServiceFactory = (logger: Logger, userService: UserService) =>  new HeroService(logger, userService.user.isAuthorized);

ファクトリ関数はUserServiceにアクセスできます。 LoggerUserServiceの両方をファクトリプロバイダーに注入して、インジェクターがファクトリ関数に渡せるようにします。

src/app/heroes/hero.service.provider.ts

      
export const heroServiceProvider = {  provide: HeroService,  useFactory: heroServiceFactory,  deps: [Logger, UserService]};
  • useFactoryフィールドは、プロバイダーがheroServiceFactoryの実装であるファクトリ関数であることを指定します。
  • depsプロパティは、プロバイダートークンの配列です。 LoggerクラスとUserServiceクラスは、それ自身のクラスプロバイダーのトークンとして機能します。 インジェクターは、指定された順序に基づいて、これらのトークンを解決し、対応するサービスをheroServiceFactoryファクトリ関数の対応するパラメーターに注入します。

エクスポートされた変数heroServiceProviderにファクトリプロバイダーをキャプチャすると、ファクトリプロバイダーを再利用できます。

値プロバイダー: useValue

useValueキーを使用すると、静的な値をDIトークンに関連付けることができます。

このテクニックを使用して、Webサイトの基本アドレスや機能フラグなどのランタイム構成定数を提供します。 また、ユニットテストで値プロバイダーを使用して、本番データサービスの代わりにモックデータを提供できます。

次のセクションでは、useValueキーの詳細について説明します。

InjectionTokenオブジェクトの使用

非クラス依存関係のプロバイダートークンとしてInjectionTokenオブジェクトを使用します。 次の例では、APP_CONFIGというトークンをInjectionTokenの型で定義しています。

src/app/app.config.ts

      
import { InjectionToken } from '@angular/core';export interface AppConfig {  title: string;}export const APP_CONFIG = new InjectionToken<AppConfig>('app.config description');

オプションの型パラメーター<AppConfig>とトークン説明app.config descriptionは、トークンの目的を指定します。

次に、APP_CONFIGInjectionTokenオブジェクトを使用して、コンポーネントに依存関係プロバイダーを登録します。

src/app/app.component.ts

      
const MY_APP_CONFIG_VARIABLE: AppConfig = {  title: 'Hello',};providers: [{ provide: APP_CONFIG, useValue: MY_APP_CONFIG_VARIABLE }]

これで、@Inject()パラメーターデコレーターを使用して、コンストラクターに構成オブジェクトを注入できます。

src/app/app.component.ts

      
export class AppComponent {  constructor(@Inject(APP_CONFIG) config: AppConfig) {    this.title = config.title;  }}

インターフェースとDI

TypeScriptのAppConfigインターフェースは、クラス内の型付けをサポートしますが、AppConfigインターフェースはDIで役割を果たしません。 TypeScriptでは、インターフェースは設計時のアーティファクトであり、DIフレームワークが使用できるランタイム表現またはトークンを持ちません。

TypeScriptがJavaScriptにトランスパイルされると、インターフェースは消えてしまいます。なぜなら、JavaScriptにはインターフェースがないからです。 Angularがランタイムでインターフェースを見つけられないため、インターフェースはトークンになることができず、インターフェースの注入もできません。

src/app/app.component.ts

      
// インターフェースをプロバイダートークンとして使用できません[{ provide: AppConfig, useValue: MY_APP_CONFIG_VARIABLE })]

src/app/app.component.ts

      
export class AppComponent {  // インターフェースをパラメータータイプとして使用して注入することはできません  constructor(private config: AppConfig) {}}