🐟

[Angular] APP_INITIALIZERで初期化ロジックを定義

2022/07/28に公開

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トークンとは

サービスを特定するためのキー。

@ngModuleprovidersで指定した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のappInitializer1appInitializer2を実行しています。

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

参考

https://angular.jp/api/core/APP_INITIALIZER

https://www.tektutorialshub.com/angular/angular-how-to-use-app-initializer/

https://medium.com/@gmurop/managing-dependencies-among-app-initializers-in-angular-652be4afce6f

https://stackoverflow.com/questions/46076051/this-appinitsi-is-not-a-function

Discussion