【Angular】アプリの初期画面表示より前に処理を行いたい

## はじめに
以前書いた[この記事](https://qiita.com/masaks/items/122ae2c43f43e4159b4a)では、データ取得からスタイル反映までにラグがあったためチラついたように見えるのが問題だった。
具体的にはこんな感じ。ネットワークが遅いとより顕著だ。
![custom-color-fail-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316318/dc6b4649-5a36-f842-8844-e95bef65d62b.gif)


そこで今回はそもそもアプリの初期画面表示よりも前に処理を行う方法を共有する。

## 初期表示前に処理を行うには`APP_INITIALIZE`を使用する
公式 → [APP_INITIALIZER](https://angular.jp/api/core/APP_INITIALIZER)
(全然書いてない…。)

初期表示前に行う関数でServiceクラスが使われている場合、そのServiceクラスが`providers:[]`に追加されている`module.ts`に追加する。※あちこちに散らばるのを防ぐ狙い
`useFactory`には初期表示前に行う関数を指定。`multi`を`true`に設定すると複数のproviderを使用できるようだ。(が正直ここはわかってない)
[参考サイト](https://stackoverflow.com/a/45477961)
> 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クラスを指定する。

```typescript:core.module.ts
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`を付けないことに注意。**

```typescript:theme-color-initializer.ts
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クラスの中身はこんな感じ。

```typescript:ThemeColorService
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を返却する。

これでひとまず準備は完了だ。

## 確認してみる
この通り表示されるときには既に色が反映されるようになった。
![custom-color-success.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316318/10891efb-c1c9-c84c-b405-b33a7a8433c3.gif)


## 参考
- [How to Use the APP_INITIALIZER Token to Hook Into the Angular Bootstrap Process](https://dzone.com/articles/how-to-use-the-app-initializer-token-to-hook-into)