はじめに
Angular には injection context と呼ばれる DI が可能なコンテキストが限られている。
アプリケーション実装中に DI するタイミングとしてよく目にするのは @Component や @Injectable のついた class の初期化時ではないだろうか?
あまり意識することはないかもしれないが injection context でない main.ts などでは DI できない。
しかし、場合によってはアプリケーション初期化時に DI をしたいときもある。今回はその方法を紹介する。
アプリケーション初期化時に DI する方法
準備
まずプロバイダーとして登録するための class を定義する。今回は以下のような class を用意した。
@Injectable({ providedIn: 'root' }) export class Tracer { #isInitialized = false; init(): void { if (this.#isInitialized) { throw new Error('Tracer is already initialized'); } // アプリケーション初期化時に必要なことを実行 this.#isInitialized = true; } doSomething(): void { if (!this.#isInitialized) { throw new Error('Tracer is not initialized.'); } // 何かの処理を実行 console.log('Running something in Tracer...'); } }
init() をアプリケーション初期化時に実行し、doSomething() は init() が呼ばれていないとエラーになる。
アプリケーション初期化時に実行したいので main.ts で DI したいところだが、前述したようにに main.ts は injection context ではないため実行時に NG0203 が発生する。
そのため別の方法を検討する必要がある。
bootstrapApplication のタイミングで呼び出せるようにする
Angular にはアプリケーション初期化処理のための DI トークンが存在する。それが APP_INITIALIZER である。
APP_INITIALIZER を使うと任意の関数をアプリケーション起動時に DI し、初期化中にその関数を実行することができる。
プロバイダーとして登録するには bootstrapApplication() の第 2 引数の ApplicationConfig に追加する。
以下がその記述例になる。
export const appConfig: ApplicationConfig = { providers: [ ... { provide: APP_INITIALIZER, useValue: () => inject(Tracer).init(), multi: true, }, ], };
これでアプリケーションの初期化時に処理を挟むことができるようになる。
APP_INITIALIZER は初期化処理用の特別なトークンで、このトークンに対して複数のプロバイダーを登録する可能性が考えられるため multi: true を使うことがお作法になっている。
また、これを簡略化した API も存在する。それが provideAppInitializer() である。
angular/packages/core/src/application/application_init.ts at 20.0.4 · angular/angular · GitHub
provideAppInitializer() を使うと以下のように書くことができる。
export const appConfig: ApplicationConfig = { providers: [ ... provideAppInitializer(() => inject(Tracer).init()), ], };
元々は APP_INITIALIZER のシンタックスシュガーとして登場したが v19 で APP_INITIALIZER が非推奨になったため、現在は provideAppInitializer() を使う方が推奨されている。
初期化の詳細を隠すために provideXxx() 関数を用意してもよい。
// tracer.ts export function provideTracer(): EnvironmentProviders { return provideAppInitializer(() => inject(Tracer).init()); } // main.ts export const appConfig: ApplicationConfig = { providers: [ ... provideTracer(), ], };
これでアプリケーション初期化時に任意の処理を挟むことができる。
おわりに
アプリケーション初期化時に必要な処理はそこまで多くない。基本的に AppComponent でやればいいことは AppComponent でやるほうがよい。
一方で、外部サービスを用いたエラートラッキングの準備などはできるだけ早い段階でおこないたい。
その場合のひとつの方法として provideAppInitializer() を覚えておくとよさそうである。