Dev Study
← 解説「InjectionToken — クラスではない値を配る」に戻る

サンプルコードで身につける: InjectionToken — クラスではない値を配る

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1InjectionToken を定義する

トークン定義の最小例です。new InjectionToken<string>('説明文字列') で「文字列を配るための目印」ができます。型引数がそのまま inject() の戻り値の型になるため、必ず付けておきます。引数の文字列はエラーメッセージに出るデバッグ用の名前です。

import { InjectionToken } from '@angular/core';

// 「APIのベースURL(string)を配る」という目印を作る
// 引数の文字列はデバッグ時にエラーメッセージへ表示される名前
export const API_BASE_URL = new InjectionToken<string>('api.base.url');

// この時点ではまだ値は決まっていない。
// 値は providers で登録して初めて配られる

2useValue で値を登録して inject で受け取る

トークンに値を結びつけて受け取るまでの一連の流れです。アプリ起動時の providers に { provide: トークン, useValue: 値 } を登録すると、どのコンポーネントやサービスからでも inject(トークン) で同じ値を受け取れます。接続先URLの定義が1か所にまとまります。

import { bootstrapApplication } from '@angular/platform-browser';
import { inject, Injectable } from '@angular/core';
import { App } from './app';
import { API_BASE_URL } from './tokens';

// 起動時にトークンと値を結びつける
bootstrapApplication(App, {
  providers: [
    { provide: API_BASE_URL, useValue: 'https://api.example.com' },
  ],
});

// サービス側: クラスと同じ書き方で値を受け取る
@Injectable({ providedIn: 'root' })
export class UserApi {
  private baseUrl = inject(API_BASE_URL); // string 型になる

  usersUrl() {
    return this.baseUrl + '/users';
  }
}

3設定オブジェクトを配る

配れるのは文字列だけでなく、オブジェクトでも構いません。アプリ名やリトライ回数のような設定を1つの型にまとめてトークンで配ると、設定の形が型で守られ、使う側は inject 一発で受け取れます。ライブラリが利用者から設定を受け取るときもこの形が使われます。

import { InjectionToken, inject, Component } from '@angular/core';

// 設定の形を型として決めておく
export type AppConfig = {
  appName: string;
  maxRetries: number;
};

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

// 起動時の providers:
// { provide: APP_CONFIG,
//   useValue: { appName: '在庫管理', maxRetries: 3 } }

@Component({
  selector: 'app-header',
  template: '<h1>{{ config.appName }}</h1>',
})
export class Header {
  // AppConfig 型として受け取れる
  config = inject(APP_CONFIG);
}

4環境ごとに useValue を切り替える

トークンの御利益が最も分かりやすい例です。開発と本番で providers に渡す値を切り替えれば、受け取る側のコードは一切変わりません。接続先URLをコンポーネントに直書きしていたら、この切り替えはアプリ中の書き換えになってしまいます。

import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app';
import { API_BASE_URL } from './tokens';

// ビルド設定などから決まる環境フラグ(例)
const isProduction = false;

bootstrapApplication(App, {
  providers: [
    {
      provide: API_BASE_URL,
      // 環境によって配る値だけを切り替える。
      // inject(API_BASE_URL) する側のコードは一切変わらない
      useValue: isProduction
        ? 'https://api.example.com'
        : 'http://localhost:3000',
    },
  ],
});

5登録忘れに備えてデフォルト値を持たせる

providers への登録を忘れると inject 時に NullInjectorError で落ちます。トークン定義時に factory を渡しておくと、未登録のときはその値が使われるため、安全なデフォルトを持つ設定が作れます。「基本はデフォルト、変えたいアプリだけ providers で上書き」という設計になります。

import { InjectionToken, inject, Component } from '@angular/core';

// factory を渡すと、providers 未登録でもこの値が使われる
export const PAGE_SIZE = new InjectionToken<number>('page.size', {
  factory: () => 20,
});

@Component({
  selector: 'app-item-list',
  template: '<p>1ページ {{ pageSize }} 件表示</p>',
})
export class ItemList {
  // 未登録なら 20。providers で上書きも可能:
  // { provide: PAGE_SIZE, useValue: 50 }
  pageSize = inject(PAGE_SIZE);
}