Angular PR

【Angular】inject関数の使い方と5つのメリット

【Angular】inject関数の使い方と5つのメリット
記事内に商品プロモーションを含む場合があります

こんにちは!

Angularで開発をしていると、必ず出会う「inject関数」。

inject関数ってそもそも何なの?
正しい使い方がわからない
依存性注入って何が嬉しいの?

このような疑問をお持ちの方も多いのではないでしょうか?

この記事では、Angularのinject関数について、基本的な使い方から応用的なテクニック、さらには具体的な活用例まで、詳しくご紹介します。

この記事は次のような人におすすめです。

この記事は次のような人におすすめ!
  • Angularのinject関数の基本を知りたい
  • 依存性注入の仕組みを理解したい
  • inject関数の具体的な使い方を学びたい

この記事を読めば、inject関数がどういうものか分かるだけでなく、具体的な実装方法も理解できるようになりますよ。

「Angularの開発経験者」「依存性注入について学びたい方」は、ぜひ参考にしてください。

それでは、順を追って詳しく見ていきましょう。

そもそもinject関数とは?

まずは、inject関数について簡単におさらいしておきましょう。

inject関数は、Angularの依存性注入(Dependency Injection)システムの一部として提供される関数です。主な役割は、サービスやその他の依存関係をコンポーネントやサービスに注入することです。

Angular 14から、@angular/coreパッケージで提供されるinject関数が登場しました。従来のデコレータベースの依存性注入に代わる、より柔軟な方法として注目を集めています。

例えば、以下のような形で使用することができます。


import { Component, inject } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-user-list',
  template: `<div>ユーザー一覧</div>`
})
export class UserListComponent {
  private userService = inject(UserService);
}

この例では、UserServiceをコンポーネントに注入しています。従来のコンストラクタインジェクションと比べて、より簡潔に記述できることが特徴です。

また、inject関数には以下のような特徴があります。

関数として呼び出せる
デコレータとは異なり、通常の関数として呼び出すことができます。
型安全
TypeScriptの型システムと完全に統合されています。
テスト容易性
モック化やスタブ化が簡単にできます。
柔軟な使用場所
コンストラクタ外でも使用可能です。

inject関数を使うメリット

inject関数を使用することには、実はたくさんのメリットがあります。ここでは、主な5つのメリットについて詳しく解説します。

コードの簡潔さ

inject関数を使用すると、依存性の注入をより簡潔に記述することができます。従来のコンストラクタインジェクションと比較すると、次のような違いがあります。


// 従来のコンストラクタインジェクション
export class UserComponent {
  constructor(private userService: UserService) {}
}

// inject関数を使用した場合
export class UserComponent {
  private userService = inject(UserService);
}

柔軟な使用場所

inject関数は、クラスのどこでも使用することができます。これは、条件付きの依存性注入や、動的な依存性の切り替えを可能にします。


export class ConfigurableComponent {
  private service = this.isProduction
    ? inject(ProductionService)
    : inject(DevelopmentService);

  private isProduction = inject(ENV_TOKEN).production;
}

型安全性の向上

TypeScriptとの相性が良く、注入される依存性の型が自動的に推論されます。これにより、型関連のエラーを早期に発見することができます。

テストのしやすさ

inject関数を使用したコードは、テストが容易になります。特に、依存性のモック化が簡単に行えます。


// テストコード
TestBed.configureTestingModule({
  providers: [
    { provide: UserService, useValue: mockUserService }
  ]
});

const component = new UserComponent();

パフォーマンスの最適化

inject関数は、Angular 14以降で導入された最新の依存性注入メカニズムを使用します。これにより、バンドルサイズの削減やパフォーマンスの向上が期待できます。

inject関数の基本的な使い方

inject関数の基本的な使い方について、具体的な例を交えながら解説します。

基本的な注入

最も基本的な使用方法は、サービスをコンポーネントに注入することです。


import { Component, inject } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-user',
  template: `
    <div *ngIf="users$ | async as users">
      <ul>
        <li *ngFor="let user of users">{{ user.name }}</li>
      </ul>
    </div>
  `
})
export class UserComponent {
  private userService = inject(UserService);
  users$ = this.userService.getUsers();
}

この例では、UserServiceをコンポーネントに注入し、ユーザー一覧を取得しています。inject関数を使用することで、コードがより簡潔になっています。

オプショナルな依存性の注入

依存性が存在しない可能性がある場合、nullableInjectOptionsを使用することができます。


import { Component, inject } from '@angular/core';
import { OptionalService } from './optional.service';

@Component({
  selector: 'app-optional',
  template: '<div>Optional Component</div>'
})
export class OptionalComponent {
  private optionalService = inject(OptionalService, { optional: true });

  doSomething() {
    if (this.optionalService) {
      // サービスが存在する場合の処理
      this.optionalService.process();
    } else {
      // サービスが存在しない場合の処理
      console.log('Service not available');
    }
  }
}

トークンを使用した注入

値やファクトリ関数を注入する場合は、InjectionTokenを使用します。まず、アプリケーションの設定でトークンを定義し、値を提供し、それをコンポーネントで注入して使用します。


// src/main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { InjectionToken } from '@angular/core';

export const API_URL = new InjectionToken<string>('API_URL');

bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: API_URL,
      useValue: 'https://api.example.com/v1'  // 実際のAPI URLを設定
    }
  ]
}).catch(err => console.error(err));

そして、コンポーネントでこのトークンを使用します。


// src/app/api.component.ts
import { Component, inject } from '@angular/core';
import { API_URL } from '../main';  // InjectionTokenをインポート

@Component({
  selector: 'app-api',
  template: '<div>API Component</div>'
})
export class ApiComponent {
  private apiUrl = inject(API_URL);

  getData() {
    // API URLを使用した処理
    console.log(`Fetching data from: ${this.apiUrl}`);
  }
}

この例のポイントはAPI_URLというトークンの扱い方です。トークンをアプリケーション全体で使えるようにprovidersに登録することで、環境ごとに異なるAPI URLの設定が可能になります。さらに、コンポーネント側ではinject関数を使って簡単に値を取得できるのが特徴です。環境変数の管理やAPIエンドポイントの切り替えなど、様々な用途に活用できる便利な手法と言えるでしょう。

複数の依存性の注入

複数のサービスを注入する場合も、それぞれに対してinject関数を使用します。


import { Component, inject } from '@angular/core';
import { AuthService } from './auth.service';
import { UserService } from './user.service';
import { LoggerService } from './logger.service';

@Component({
  selector: 'app-multi',
  template: '<div>Multi-injection Component</div>'
})
export class MultiComponent {
  private authService = inject(AuthService);
  private userService = inject(UserService);
  private logger = inject(LoggerService);

  async login(username: string, password: string) {
    try {
      const user = await this.authService.login(username, password);
      await this.userService.setCurrentUser(user);
      this.logger.log('User logged in successfully');
    } catch (error) {
      this.logger.error('Login failed', error);
    }
  }
}

このように、inject関数を活用することで、複数の依存性を柔軟かつ型安全に扱えます。開発効率の向上にもつながる便利な手法と言えるでしょう。

inject関数の応用的な使い方

inject関数の応用的な使い方について、具体的な例を交えながら解説していきます。

条件付き注入

環境や条件に応じて異なるサービスを注入したい場合、inject関数を条件分岐と組み合わせることができます。


import { Component, inject, InjectionToken } from '@angular/core';
import { Environment } from './environment.interface';

const ENV = new InjectionToken<Environment>('env');

@Component({
  selector: 'app-conditional',
  template: '<div>Conditional Injection</div>'
})
export class ConditionalComponent {
  private env = inject(ENV);
  private apiService = this.env.production
    ? inject(ProductionApiService)
    : inject(DevelopmentApiService);
}

ファクトリパターンとの組み合わせ

複雑な初期化ロジックが必要な場合、ファクトリパターンと組み合わせることができます。


import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

export function createConfigService(http: HttpClient) {
  return new ConfigService(http);
}

@Injectable({
  providedIn: 'root',
  useFactory: () => {
    const http = inject(HttpClient);
    return createConfigService(http);
  }
})
export class ConfigService {
  constructor(private http: HttpClient) {}
}

カスタムフック関数の作成

よく使う依存性の注入パターンを、カスタムフック関数としてまとめることができます。


import { inject } from '@angular/core';
import { AuthService } from './auth.service';
import { UserService } from './user.service';

export function useAuth() {
  const authService = inject(AuthService);
  const userService = inject(UserService);

  return {
    login: async (username: string, password: string) => {
      const user = await authService.login(username, password);
      await userService.setCurrentUser(user);
      return user;
    },
    logout: async () => {
      await authService.logout();
      await userService.clearCurrentUser();
    }
  };
}

// 使用例
@Component({
  selector: 'app-login',
  template: '<div>Login Component</div>'
})
export class LoginComponent {
  private auth = useAuth();

  async handleLogin(username: string, password: string) {
    try {
      await this.auth.login(username, password);
    } catch (error) {
      console.error('Login failed', error);
    }
  }
}

依存性の遅延注入

パフォーマンス最適化のため、必要になった時点で依存性を注入することができます。


import { Component, inject } from '@angular/core';
import { HeavyService } from './heavy.service';

@Component({
  selector: 'app-lazy',
  template: '<div>Lazy Injection</div>'
})
export class LazyComponent {
  private heavyService?: HeavyService;

  async handleClick() {
    // クリック時に初めてサービスを注入
    if (!this.heavyService) {
      this.heavyService = inject(HeavyService);
    }
    await this.heavyService.process();
  }
}

テスト時の活用

テストコードでは、inject関数を使用することで、依存性のモック化が容易になります。


import { TestBed } from '@angular/core/testing';
import { UserComponent } from './user.component';
import { UserService } from './user.service';

describe('UserComponent', () => {
  const mockUserService = {
    getUsers: jest.fn()
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [UserComponent],
      providers: [
        { provide: UserService, useValue: mockUserService }
      ]
    });
  });

  it('should fetch users on init', () => {
    const component = TestBed.createComponent(UserComponent);
    expect(mockUserService.getUsers).toHaveBeenCalled();
  });
});

注意点とベストプラクティス

inject関数を使用する際の注意点とベストプラクティスについて解説します。

クラスプロパティでの使用

inject関数をクラスプロパティとして使用する場合は、必ず初期化時に呼び出す必要があります。


// Good
private userService = inject(UserService);

// Bad - 初期化されていない
private userService: UserService;
ngOnInit() {
  this.userService = inject(UserService); // 実行時エラーが発生
}

循環依存の回避

inject関数を使用しても、循環依存は避ける必要があります。


// Bad - 循環依存の例
@Injectable({ providedIn: 'root' })
export class ServiceA {
  private serviceB = inject(ServiceB);
}

@Injectable({ providedIn: 'root' })
export class ServiceB {
  private serviceA = inject(ServiceA);
}

代わりに、以下のような設計を検討しましょう。


// Good - 共通のインターフェースを使用
interface CommonInterface {
  process(): void;
}

@Injectable({ providedIn: 'root' })
export class ServiceA implements CommonInterface {
  process() { /* ... */ }
}

@Injectable({ providedIn: 'root' })
export class ServiceB implements CommonInterface {
  private common = inject(CommonInterface);
  process() { /* ... */ }
}

パフォーマンスへの考慮

inject関数の使用は、パフォーマンスにも影響を与える可能性があります。


// Bad - 毎回inject関数を呼び出す
@Component({
  template: '<div *ngFor="let item of items">{{ getService().process(item) }}</div>'
})
export class BadComponent {
  getService() {
    return inject(ProcessService); // 毎回新しいインスタンスを取得
  }
}

// Good - 一度だけinject関数を呼び出す
@Component({
  template: '<div *ngFor="let item of items">{{ processService.process(item) }}</div>'
})
export class GoodComponent {
  private processService = inject(ProcessService);
}

まとめ

ここまで、Angularのinject関数について詳しく解説してきました。改めて、重要なポイントをおさらいしましょう。

  • inject関数は、Angular 14以降で導入された新しい依存性注入の方法
  • コードの簡潔さと柔軟性を両立できる
  • TypeScriptとの相性が良く、型安全性が高い
  • テストが容易で、モック化やスタブ化が簡単
  • 様々な場面で活用でき、具体的な実装の幅が広い
  • パフォーマンスと保守性を考慮した使用が重要
  • 適切な設計と組み合わせることで、より良いコードを書ける

inject関数は、Angularアプリケーションの開発において非常に強力なツールです。しかし、その使用には適切な理解と設計が必要です。

これまでの内容を参考に、ぜひ自分のプロジェクトでもinject関数を活用してみてください。適切に使用することで、より保守性が高く、テストしやすいコードを書くことができるはずです。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です