angular-di

angular-di

熱門

在 Angular v20+ 中使用 inject()、注入令牌和提供者配置來實作依賴注入。適用於服務架構、在不同層級提供依賴、建立可注入令牌,以及管理單例與範圍服務。觸發時機:建立服務、設定提供者、使用注入令牌,或理解 DI 階層。

596星標
70分支
更新於 2026/3/23
SKILL.md
唯讀
名稱
angular-di
描述

在 Angular v20+ 中使用 inject()、注入令牌和提供者配置來實作依賴注入。適用於服務架構、在不同層級提供依賴、建立可注入令牌,以及管理單例與範圍服務。觸發時機:建立服務、設定提供者、使用注入令牌,或理解 DI 階層。

Angular 依賴注入

使用 inject() 和提供者在 Angular v20+ 中配置和使用依賴注入。

基本注入

使用 inject()

建議優先使用 inject() 而非建構子注入:

import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { User } from './user.service';

@Component({
  selector: 'app-user-list',
  template: `...`,
})
export class UserList {
  // 注入依賴
  private http = inject(HttpClient);
  private userService = inject(User);
  
  // 可立即使用
  users = this.userService.getUsers();
}

可注入服務

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

@Injectable({
  providedIn: 'root', // 根層級單例
})
export class User {
  private http = inject(HttpClient);
  
  private users = signal<User[]>([]);
  readonly users$ = this.users.asReadonly();
  
  async loadUsers() {
    const users = await firstValueFrom(
      this.http.get<User[]>('/api/users')
    );
    this.users.set(users);
  }
}

提供者範圍

根層級(單例)

// 建議使用 providedIn
@Injectable({
  providedIn: 'root',
})
export class Auth {}

// 或在 app.config.ts 中
export const appConfig: ApplicationConfig = {
  providers: [
    Auth,
  ],
};

元件層級(每個元件一個實例)

@Component({
  selector: 'app-editor',
  providers: [EditorState], // 每個元件新實例
  template: `...`,
})
export class Editor {
  private editorState = inject(EditorState);
}

路由層級

export const routes: Routes = [
  {
    path: 'admin',
    providers: [Admin], // 在此路由樹內共享
    children: [
      { path: '', component: AdminDashboard },
      { path: 'users', component: AdminUsers },
    ],
  },
];

注入令牌

建立令牌

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

// 簡單值令牌
export const API_URL = new InjectionToken<string>('API_URL');

// 物件令牌
export interface AppConfig {
  apiUrl: string;
  features: {
    darkMode: boolean;
    analytics: boolean;
  };
}

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

// 帶工廠的令牌(自我提供)
export const WINDOW = new InjectionToken<Window>('Window', {
  providedIn: 'root',
  factory: () => window,
});

export const LOCAL_STORAGE = new InjectionToken<Storage>('LocalStorage', {
  providedIn: 'root',
  factory: () => localStorage,
});

提供令牌值

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    { provide: API_URL, useValue: 'https://api.example.com' },
    {
      provide: APP_CONFIG,
      useValue: {
        apiUrl: 'https://api.example.com',
        features: { darkMode: true, analytics: true },
      },
    },
  ],
};

注入令牌

@Injectable({ providedIn: 'root' })
export class Api {
  private apiUrl = inject(API_URL);
  private config = inject(APP_CONFIG);
  private window = inject(WINDOW);
  
  getBaseUrl(): string {
    return this.apiUrl;
  }
}

提供者類型

useClass

// 提供實作
{ provide: Logger, useClass: ConsoleLogger }

// 條件式實作
{
  provide: Logger,
  useClass: environment.production
    ? ProductionLogger
    : ConsoleLogger,
}

useValue

// 靜態值
{ provide: API_URL, useValue: 'https://api.example.com' }

// 設定物件
{ provide: APP_CONFIG, useValue: { theme: 'dark', language: 'en' } }

useFactory

// 帶依賴的工廠
{
  provide: User,
  useFactory: (http: HttpClient, config: AppConfig) => {
    return new User(http, config.apiUrl);
  },
  deps: [HttpClient, APP_CONFIG],
}

// 非同步工廠(不建議,請使用 provideAppInitializer)
{
  provide: CONFIG,
  useFactory: () => fetch('/config.json').then(r => r.json()),
}

useExisting

// 別名指向現有提供者
{ provide: AbstractLogger, useExisting: ConsoleLogger }

// 多個令牌指向同一實例
providers: [
  ConsoleLogger,
  { provide: Logger, useExisting: ConsoleLogger },
  { provide: ErrorLogger, useExisting: ConsoleLogger },
]

注入選項

可選注入

@Component({...})
export class My {
  // 若未提供則回傳 null
  private analytics = inject(Analytics, { optional: true });
  
  trackEvent(name: string) {
    this.analytics?.track(name);
  }
}

Self、SkipSelf、Host

@Component({
  providers: [Local],
})
export class Parent {
  // 僅在此元件的注入器中查找
  private local = inject(Local, { self: true });
}

@Component({...})
export class Child {
  // 跳過此元件,在父層查找
  private parentService = inject(ParentSvc, { skipSelf: true });

  // 僅向上查找到宿主元件
  private hostService = inject(Host, { host: true });
}

多重提供者

為同一令牌收集多個值:

// 多個驗證器的令牌
export const VALIDATORS = new InjectionToken<Validator[]>('Validators');

// 提供多個值
providers: [
  { provide: VALIDATORS, useClass: RequiredValidator, multi: true },
  { provide: VALIDATORS, useClass: EmailValidator, multi: true },
  { provide: VALIDATORS, useClass: MinLengthValidator, multi: true },
]

// 以陣列形式注入
@Injectable()
export class Validation {
  private validators = inject(VALIDATORS); // Validator[]
  
  validate(value: string): ValidationError[] {
    return this.validators
      .map(v => v.validate(value))
      .filter(Boolean);
  }
}

HTTP 攔截器(多重提供者)

// 攔截器內部使用多重提供者
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([
        authInterceptor,
        loggingInterceptor,
        errorInterceptor,
      ])
    ),
  ],
};

應用程式初始化器

使用 provideAppInitializer 在應用程式啟動前執行非同步程式碼:

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

export const appConfig: ApplicationConfig = {
  providers: [
    Config,
    provideAppInitializer(() => {
      const configService = inject(Config);
      return configService.loadConfig();
    }),
  ],
};

多個初始化器

providers: [
  provideAppInitializer(() => {
    const config = inject(Config);
    return config.load();
  }),
  provideAppInitializer(() => {
    const auth = inject(Auth);
    return auth.checkSession();
  }),
]

環境注入器

以程式方式建立注入器:

import { createEnvironmentInjector, EnvironmentInjector, inject } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class Plugin {
  private parentInjector = inject(EnvironmentInjector);
  
  loadPlugin(providers: Provider[]): EnvironmentInjector {
    return createEnvironmentInjector(providers, this.parentInjector);
  }
}

runInInjectionContext

在注入上下文中執行程式碼:

import { runInInjectionContext, EnvironmentInjector, inject } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class Utility {
  private injector = inject(EnvironmentInjector);
  
  executeWithDI<T>(fn: () => T): T {
    return runInInjectionContext(this.injector, fn);
  }
}

// 使用方式
utilityService.executeWithDI(() => {
  const http = inject(HttpClient);
  // 使用 http...
});

如需進階模式,請參閱 references/di-patterns.md