はじめに
あるDIトークンを別のDIトークンにマッピングすることができる、プロバイダーキーuseExisting
について説明します。
基本的なプロバイダー設定
useExisting
について触れる前に、基本的なプロバイダーの設定についておさらいしていきます。
下記のようにプロバイダーを設定すると、一件のSampleService
インスタンスが作成され、DIトークンSampleService
に関連づけられます。
@Injectable({ providedIn: 'root', }) export class SampleService {}
もしくは
providers: [SampleService]
なお、providers: [SampleService]
は、[{ provide: SampleService, useClass: SampleService }]
のシンタックスシュガーのため、どちらも同じ意味になります。
上記でプロバイダー設定されているSampleServie
をコンポーネントやサービスクラスに注入するときは、以下のようにプロバイダーで指定されているDIトークンSampleService
の型を使用する必要があります。
constructor(heroService: HeroService)
上記の記法で、SampleServie
インスタンスを複数のDIトークン(SampleService
とBasicService
)に紐づけようとすると下記のような書き方になると思います。
providers: [ SampleService, { provide: BasicService, useClass: SampleService }, ],
また、上記で定義したサービスをコンポーネントやサービスに注入しようとすると下記のような書き方になります。
constructor( private sampleService: SampleService, private basicService: BasicService ) {}
上記の書き方だと、確かにSampleServie
インスタンスは複数のDIトークン(SampleService
とBasicService
)に紐づきますが、その代わりSampleServie
インスタンスが複数件(上記の例だと2件)作成されます。
シングルトンとして使用されることを意図したサービスのインスタンスを複数件作成してしまった場合、期待結果通りにシステムが動かなくなることがあります。*1
このように、一件のサービスインスタンスに複数のDIトークンを紐づけたい場合には、useExisting
が役に立ちます。
useExisting
について
プロバイダーキーのuseExisting
は、あるDIトークンを別のDIトークンにマッピングすることができるため、一つのサービスインスタンスに複数のDIトークンを紐づける、といった使い方ができます。
例えば、SampleService
を2件のDIトークン(SampleService
とBasicService
)に紐づける場合には、下記のような書き方ができます。
providers: [ SampleService, { provide: BasicService, useExisting: SampleService }, ],
上記の書き方は、useClass
を使った下記の書き方と同様の意味になります。
providers: [ { provide: SampleService, useClass: SampleService }, { provide: BasicService, useExisting: SampleService }, ],
上記のようにuseExisting
を使用してプロバイダー設定を書くことで、SampleService
のインスタンスを複数件作成せずに、SampleService
のインスタンスを複数のDIトークンに紐づけることができます。
流れとしては、下記のようになります。
{ provide: SampleService, useClass: SampleService }
により、SampleService
のインスタンスが1件作成されたあと、DIトークンSampleService
に割り当てられるuseExisting
を使うことでDIトークンSampleService
をDIトークンBasicService
にマッピングする結果として、2件のDIトークン(
SampleService
とBasicService
)が同じ1件のSampleService
インスタンスに関連付けられる
使用例
useExisting
の具体的な使用例として、カスタムエラーハンドラーを直接呼び出す時に使ったりします。
カスタムエラーハンドラーを直接呼び出す
Angularでカスタムエラーハンドラーを定義するには、以下のような設定をする必要があります。
@Injectable() export class SampleErrorHandler implements ErrorHandler { public handleError(error: any): void { console.error(error); } public openErrorWindow(): void { window.alert('エラーが発生しました'); } } @NgModule({ providers: [{ provide: ErrorHandler, useClass: SampleErrorHandler }] }) class SampleModule {}
この状態でカスタムエラーハンドラーSampleErrorHandler
のメソッドopenErrorWindow()
を下記のようにコンポーネントから呼び出そうとするとNullInjectorError
エラーで失敗します。
export class SampleComponent { constructor(private sampleErrorHandler: SampleErrorHandler) { this.sampleErrorHandler.openErrorWindow(); } }
発生するエラー
NullInjectorError: R3InjectorError(SampleModule)[SampleErrorHandler -> SampleErrorHandler -> SampleErrorHandler]: NullInjectorError: No provider for SampleErrorHandler!
上記のようにNullInjectorError
が発生するのは、SampleErrorHandler
インスタンスがDIトークンErrorHandler
にしか関連付けられていないのに、コンポーネントのSampleComponent
でDIトークンSampleErrorHandler
からSampleErrorHandler
インスタンスを参照しようとしているのが原因です。
ここで、useExisting
を使って下記のように設定すると、エラーを発生させずにカスタムエラーハンドラーのメソッドを直接呼ぶことができるようになります。
@Injectable() export class SampleErrorHandler implements ErrorHandler { public handleError(error: any): void { console.error(error); } public openErrorWindow(): void { window.alert('エラーが発生しました'); } } @NgModule({ providers: [ { provide: ErrorHandler, useClass: SampleErrorHandler }, { provide: SampleErrorHandler, useExisting: ErrorHandler }, ], }) class SampleModule {} @Component({ selector: 'sample-root', templateUrl: './sample.component.html', styleUrls: ['./sample.component.scss'], }) export class SampleComponent { constructor(private sampleErrorHandler: SampleErrorHandler) { this.sampleErrorHandler.openErrorWindow(); } }
上記では、useExisting
により、カスタムエラーハンドラーSampleErrorHandler
のインスタンスが2件のDIトークン(ErrorHandler
とSampleErrorHandler
)に紐づけられることで、コンポーネントSampleComponent
からカスタムエラーハンドラーのopenErrorWindow()
メソッドを呼び出すことができるようになっています。
参考サイト
https://angular.jp/guide/dependency-injection-in-action
https://angular.jp/guide/singleton-services
*1:サービスが持っているプロパティが期待結果通り更新されないなどの意図しない動作が発生する可能性がある