[Angular] APP_INITIALIZERで初期化ロジックを定義
APP_INITIALIZERとは
💡 アプリケーション初期化前に実行される関数
APP_INITIALIZERはInjectionTokenのインスタンスです。
これはAngularが提供する組み込みのInjectionTokenです。
Angularはアプリケーションがロードされると、このトークンによって提供される関数を実行します。
もし、関数がPromise/Obserbavleを返した場合、AngularはPromiseやObservableが解決・完了されるまで待ちます。これはアプリケーションが初期化される前に初期化ロジックを実行するのに最適な場所となります。
実行順序としては、最初に読み込まれるルートコンポーネント(@NgModuleのbootstrapで指定したコンポーネント)のコンストラクターよりも先に読み込まれます。次にライフサイクルメソッドが順番に呼び出されます。
ちなみに、NgRxを利用している場合は、ReducerのInitialState
がAPP_INITIALIZERより先に読み込まれます。
依存性注入のまとめ
AngularはDIトークンを使ってAngular Providerにある依存関係(サービスやオブジェクト)を探します。
例えば、コンポーネントでサービスを利用する場合は、このDIという仕組みを利用し、必要なサービスのキーであるDIトークンで、依存しているサービスを検索してコンポーネントに注入します。
このDIトークンを使って依存関係をProviderに登録します。
この仕組みのおかげで、モジュールやコンポーネントはサービスクラスに依存しない状態になっています。
providers :[{ provide: token, useClass: userService }]
- トークンは文字列またはInjectionTokenのインスタンスのいづれかの型になります。
DIトークンとは
サービスを特定するためのキー。
@ngModule
のproviders
で指定したprovide
プロパティの値(サービス名やオブジェクト名)や、
コンポーネントのコンストラクタで指定した引数型(サービス名やオブジェクト名)がそれにあたります。
型トークン
providers :[{ provide: userService, useClass: userService }]
文字列トークン
providers :[ { provide:'Hello', useValue: 'Angular' }]
InjectionToken
InjectionToken は、インターフェイスや呼び出し可能な型、配列など、使用する型が実行時の表現を持っていない場合に使用されます。
export const MESSAGE = new InjectionToken<string>('Hello Angular');
providers :[ { provide: HELLO_MESSAGE, useValue: 'Hello World!' }];
APP_INITIALIZERを使用する場所
前述した通り、APP_INITIALIZERはアプリケーションが初期化される前に実行されます。
APP_INITIALIZERが提供するすべて関数が完了するまで、Angularはアプリの初期化を一時停止します。
もし、これがPromiseやObservableを返した場合は、これらが解決されるまで待ちます。
これにより初期化プロセスにフックして、アプリケーションカスタムロジックを実行する機会が得られます。
実行時の設定情報を読み込んだり、HttpClientでAPIを叩いて必要なデータを取得したりできます。
APP_INITIALIZERの使用例
getApiService.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { AppConfig } from '../utilities/app-config';
@Injectable({
providedIn: 'root'
})
export class getApiService{
private url: string;
constructor(
private httpClient: HttpClient
) { }
return this.httpClient
.get<any>('http://XXXXXX.XXXx')
.toPromise()
.then(url=> this.url= config)
}
}
}
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AboutUsComponent } from './about-us.component';
import { HomeComponent } from './home.component';
import { ContactUsComponent } from './contact-us.component';
import { getApiService} from './api.service';
const appInitializer = (configService: getApiService) => {
return () => {
return configService.getApi();
};
};
@NgModule({
declarations: [
AppComponent, AboutUsComponent,HomeComponent,ContactUsComponent
],
imports: [
HttpClientModule,
BrowserModule,
AppRoutingModule,
],
providers: [
AppInitService,
{
provide: APP_INITIALIZER,
useFactory: appInitializer,
deps: [AppInitService],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
-
@angular/core
から APP_INITIALIZER をインポート -
appInitializer
は関数を返す必要があります。 -
appInitializer
を実行する必要がありますが、プロバイダから直接実行することはできません。
configService.getApi()
を呼び出して、Promise/Observableを返す関数を作成する必要があります。
これはappInitializer
関数で行います。(getApiService) - 最後にAPP_INITIALIZER トークンを使用して
appInitializer
を提供します。
providers: [
AppInitService,
{
provide: APP_INITIALIZER,
useFactory: appInitializer,
deps: [AppInitService],
multi: true
}
provideプロパティ
使用するトークンを指定します。ここではAPP_INITIALIZER。
userFactoryプロパティ
appInitializer
はクラスではなく、関数なので、userFactoryを使用します。Angular Injectorはこれに指定した関数を実行します。
depsプロパティ
getApiServiceのインスタンスを作成し、appInitializer
関数に注入する必要があることをAngularに伝えます。
multiプロパティ
同一のトークンに対して複数のProviderが登録された場合、デフォルト(false
)では後から定義したサービスが上書きされて呼び出される。
true
にした場合、両方のサービスが呼び出される。
マルチプロバイダを利用する場合
multi: true
を使用すると、Multi Providerトークンを作成することができます。
複数のAPP_INITIALIZERを作成できます。
つまり、複数の関数またはサービスを初期化中に呼び出すことができることを意味します。
下記は、getApiServiceのappInitializer1
とappInitializer2
を実行しています。
providers: [
AppInitService,
{ provide: APP_INITIALIZER,useFactory: appInitializer1 , deps: [AppInitService], multi: true},
{ provide: APP_INITIALIZER,useFactory: appInitializer2, multi: true}
],
💡 これら二つの関数が、お互いを待つことなく実行されます。
あるAPP_INITIALIZERを他のAPP_INITIALIZERより先に実行するなど、制御をする場合は注意。
下記参考
Managing dependencies among App Initializers in Angular
参考
Discussion