はじめに
以前書いたこの記事では、データ取得からスタイル反映までにラグがあったためチラついたように見えるのが問題だった。
具体的にはこんな感じ。ネットワークが遅いとより顕著だ。

そこで今回はそもそもアプリの初期画面表示よりも前に処理を行う方法を共有する。
初期表示前に処理を行うにはAPP_INITIALIZEを使用する
公式 → APP_INITIALIZER
(全然書いてない…。)
初期表示前に行う関数でServiceクラスが使われている場合、そのServiceクラスがproviders:[]に追加されているmodule.tsに追加する。※あちこちに散らばるのを防ぐ狙い
useFactoryには初期表示前に行う関数を指定。multiをtrueに設定すると複数のproviderを使用できるようだ。(が正直ここはわかってない)
参考サイト
With multi: true providing multiple providers with the same key (APP_INITIALIZER) won't override the previous one (behavior with multi: false), but DI will collect them in an array itself
depsにuseFactoryで指定されている関数内で使用されるServiceクラスを指定する。
import { APP_INITIALIZER, NgModule} from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { ThemeColorService } from './services/theme-color.service';
import { themeColorInitializer } from './app-initializer/theme-color-initializer';
@NgModule({
declarations: [],
imports: [CommonModule, HttpClientModule],
providers: [
// これを追加↓
{
provide: APP_INITIALIZER,
useFactory: themeColorInitializer,
multi: true,
deps: [ThemeColorService],
},
ThemeColorService,
],
})
export class CoreModule {
constructor() { }
}
useFactoryに渡される関数を実装
今回の例で言うと「テーマカラーを取得し、セットする」処理を実装。
既存のServiceクラスを使いたい場合は引数にそのまま渡す。privateを付けないことに注意。
import { ThemeColorService } from '../services/theme-color.service';
/**
* アプリのテーマカラーの取得をAngularアプリがレンダリングする前に行う。
* @param appThemeColor
*/
export function themeColorInitializer(appThemeColor: ThemeColorService) {
return () => appThemeColor.initialize();
}
ちなみにこのファクトリー関数は、「Promiseを返却する関数」を返却する関数を実装する必要がある。 このPromiseが解決されて初めてAngularアプリのBootstrapプロセスが続行されるような仕組み。
さらにここではHttpClientも使用でき、その場合はmodule.tsのimportsにHttpClientModule、depsにHttpClientを含める必要がある。(今回の場合だとServiceクラスでラップしているのでdepsへの追加はServiceクラスだけで大丈夫。)
The factory function is expected to return a Promise and only after the promise has resolved will the application bootstrap process continue
...
larly, you could use an HTTP service (from HttpClient) to request data from an API and only after the request is complete that the application bootstrap process would continue. However, to use Angular's HttpClient in the initApp factory function, include HttpClientModule in the imports section and also specify HttpClient as a dependency ( deps) to the APP_INITIALIZER provider
ついでにこのServiceクラスの中身はこんな感じ。
import { Injectable } from '@angular/core';
import { catchError, tap } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
interface ThemeColor {
themeColor: string;
}
@Injectable({
providedIn: 'root',
})
export class ThemeColorService {
constructor(private http: HttpClient) {}
/**
* テーマカラーの初期化を行う
*/
public async initialize() {
const id = 1; // ここはアプリ毎に可変だが一旦仮置き
return this.http
.get<ThemeColor>(`https://example.com/app/${id}/theme-color`)
.pipe(
tap(({themeColor}) => ThemeColorService.setThemeColor(themeColor)),
catchError(() => ThemeColorService.setDefaultColor())
)
.toPromise();
}
/**
* テーマカラーを反映する
*/
private static setThemeColor(themeColor: string) {
document.documentElement.style.setProperty('--main-color', themeColor);
}
/**
* デフォルトのテーマカラーをセットする。
*/
private static async setDefaultColor() {
document.documentElement.style.setProperty('--main-color', 'red');
}
}
取得に成功したらテーマカラーをセット、失敗したらデフォルトカラーをセットし最後にtoPromise()でPromiseを返却する。
これでひとまず準備は完了だ。
