Angular での高度な依存性注入
La 依存性注入 (DI) それは Angular のアーキテクチャの中心です。バージョン2以降、
このフレームワークは強力で洗練された DI システムを提供していましたが、Angular 14 以降と
スタンドアロンコンポーネントに比べて、システムは根本的に進化しました。新しい API のような inject()、
ツリーシェイキング可能なプロバイダー、ガード、機能的インターセプターにより、管理方法が変わりました
最新の Angular アプリケーションの依存関係。
シリーズ第 9 回の記事では、 モダンアンギュラー、依存関係の注入について調べます。
高度な: 新しい機能から inject() の階層からカスタム トークンへ
インジェクターからマルチプロバイダーパターンまで、コンポーネントのテストと動的作成まで
インジェクションコンテキスト。 DI の基本をすでにマスターしている場合は、この記事を参照してください。
次に。
何を学ぶか
- 機能
inject()メーカー経由のインジェクションを置き換えるため - 作成方法と使用方法
InjectionToken典型的な - インジェクターの完全な階層: ルート、プラットフォーム、要素、コンポーネント
- オプション
providedInバンドルへの影響 - パターン
MULTIインターセプター、ガード、複数のプロバイダー向け - ツリーシェイク可能なプロバイダーとバンドルサイズの最適化
- 機能的なガードとインターセプター
CanActivateFnewithInterceptors() EnvironmentInjectorコンポーネントの動的な作成runInInjectionContextユーティリティ関数の場合inject()- DIテスト
TestBedeoverrideProvider
最新の Angular シリーズの概要
| # | アイテム | 集中 |
|---|---|---|
| 1 | 角度信号 | きめ細かい応答性 |
| 2 | ゾーンレスの変更検出 | Zone.jsを削除する |
| 3 | 新しいテンプレート @if、@for、@defer | 最新の制御フロー |
| 4 | スタンドアロンコンポーネント | NgModule を使用しないアーキテクチャ |
| 5 | 信号形式 | シグナルを使用したレスポンシブフォーム |
| 6 | SSR と増分水分補給 | サーバーサイドレンダリング |
| 7 | Angular におけるコア Web バイタル | パフォーマンスと指標 |
| 8 | 角度のある PWA | プログレッシブ Web アプリ |
| 9 | 現在地 - 高度な依存性注入 | DIツリーシェイク可能 |
| 10 | Angular 17 から 21 への移行 | 移行ガイド |
1. inject() 関数: Goodbye コンストラクター
機能 inject()は、Angular 14 で安定した API として導入され、
現代の DI における最も重要な変化。依存関係をパラメータとして宣言する代わりに
コンストラクターのどこからでも呼び出すことができる関数を使用します。
インジェクションコンテキスト active: コンストラクター内、フィールド初期化子内、
ファクトリ関数とユーティリティ関数で。
// PRIMA: Iniezione via costruttore (approccio tradizionale)
@Component({
selector: 'app-user-list',
standalone: true,
template: '...'
})
export class UserListComponent {
constructor(
private userService: UserService,
private router: Router,
private route: ActivatedRoute,
@Inject(API_URL) private apiUrl: string,
@Optional() private logger?: LoggerService
) {}
}
// DOPO: Funzione inject() (approccio moderno)
@Component({
selector: 'app-user-list',
standalone: true,
template: '...'
})
export class UserListComponent {
private userService = inject(UserService);
private router = inject(Router);
private route = inject(ActivatedRoute);
private apiUrl = inject(API_URL);
private logger = inject(LoggerService, { optional: true });
// I campi sono già disponibili, non serve il costruttore
users = this.userService.getUsers();
}
コンストラクターに対する inject() の利点
| 待ってます | ビルダー | インジェクト() |
|---|---|---|
| 可読性 | パラメータが長く、管理が難しい | インラインステートメント、明確かつ簡潔 |
| 遺伝 | 必要 super() すべてのパラメータを使用して |
問題ありません、キャンプは自律的です |
| 再利用 | Angular クラス コンストラクター内のみ | 機能、工場、ユーティリティで使用可能 |
| タイプセーフティ | デコレータを完備 | 完全な自動型推論 |
| 木が揺れる | デコレータのメタデータによる制限 | クラスへの最適な直接参照 |
| オプション/セルフ/スキップセルフ | デコレータ: @Optional(), @Self() |
アイテムオプション: { optional: true, self: true } |
inject() を使用できる場所
機能 inject() 1つでのみ機能します インジェクションコンテキスト。
このコンテキスト外で呼び出すと実行時エラーが生成されます
NG0203: inject() must be called from an injection context.
- 許可された: コンポーネント/サービス/ディレクティブ/パイプ コンストラクター、フィールド初期化子、プロバイダー ファクトリ関数、実行される関数
runInInjectionContext() - 許可されないもの: 初期化後に呼び出されるコンポーネントメソッド、イベントコールバック、setTimeout/setInterval、コンテキストのないスタンドアロン関数
特に強力なパターンの 1 つは、 inject() 作成する
再利用可能なユーティリティ関数 これは注入ロジックをカプセル化します。
// utils/navigation.ts
export function injectRouteParams() {
const route = inject(ActivatedRoute);
return toSignal(
route.paramMap.pipe(
map(params => ({
id: params.get('id'),
slug: params.get('slug')
}))
)
);
}
export function injectQueryParams() {
const route = inject(ActivatedRoute);
return toSignal(route.queryParamMap);
}
// Uso nel componente
@Component({ ... })
export class ProductDetailComponent {
params = injectRouteParams();
// params() restituisce { id: '123', slug: 'my-product' }
}
2. InjectionToken: 非クラス値の型付きトークン
依存関係がクラスではなくプリミティブ値(文字列、数値、オブジェクトの場合)
構成)、Angular ではクラス自体を検索キーとして使用できません。
InjectionToken この問題は、独自の型付きトークンを作成することで解決します。
これらは DI レジスタのキーとして機能します。
import { InjectionToken, inject } from '@angular/core';
// Token con valore statico
export const API_BASE_URL = new InjectionToken<string>(
'API_BASE_URL'
);
// Token con factory function (tree-shakable!)
export const IS_MOBILE = new InjectionToken<boolean>(
'IS_MOBILE',
{
providedIn: 'root',
factory: () => {
const platform = inject(PLATFORM_ID);
if (isPlatformBrowser(platform)) {
return window.innerWidth < 768;
}
return false;
}
}
);
// Token per configurazione complessa
export interface AppConfig {
apiUrl: string;
production: boolean;
features: {
darkMode: boolean;
analytics: boolean;
};
}
export const APP_CONFIG = new InjectionToken<AppConfig>(
'APP_CONFIG',
{
providedIn: 'root',
factory: () => ({
apiUrl: 'https://api.example.com',
production: false,
features: {
darkMode: true,
analytics: true
}
})
}
);
// Uso nei componenti
@Component({ ... })
export class AppComponent {
private config = inject(APP_CONFIG);
private isMobile = inject(IS_MOBILE);
}
InjectionToken を使用する場合
- 設定値: API URL、機能フラグ、タイムアウト、制限
- ブラウザAPI:
window,document,localStorage(SSR互換性のため) - 交換可能な実装: ロギング戦略、ストレージアダプター、認証プロバイダー
- 動的なファクトリー: 他のサービスに依存する実行時に計算される値
3. インジェクターの階層
Angular における DI の最も強力な (そして複雑な) 側面の 1 つは、 階層構造。単一の依存関係コンテナーはありません。 Angular は、アプリケーションの構造を反映するインジェクターのツリーを維持します。 コンポーネントに依存関係が必要な場合、Angular は依存関係が見つかるまでツリーを上に移動します。
Angular のインジェクター階層
+---------------------------------------------+
| Platform Injector |
| (condiviso tra applicazioni Angular) |
| Provider: PLATFORM_ID, PLATFORM_INITIALIZER|
+----------------------+----------------------+
|
+----------------------v----------------------+
| Root Injector |
| (providedIn: 'root', app.config.ts) |
| Provider: HttpClient, Router, servizi root |
+----------------------+----------------------+
|
+---------------+---------------+
| |
+------v------+ +-------v-----+
| Module | | Module |
| Injector | | Injector |
| (lazy mod.) | | (lazy mod.) |
+------+------+ +------+------+
| |
+------v------+ +-------v-----+
| Element | | Element |
| Injector | | Injector |
| (component) | | (component) |
+------+------+ +------+------+
| |
+------v------+ +-------v-----+
| Element | | Element |
| Injector | | Injector |
| (child) | | (child) |
+-------------+ +-------------+
階層のレベル
| レベル | ほうき | 創造 | 一般的な使用方法 |
|---|---|---|---|
| プラットフォーム | ページ上のすべての Angular アプリ | platformBrowserDynamic() |
マイクロフロントエンド間の共有サービス |
| Root | アプリケーション全体 (シングルトン) | providedIn: 'root' o app.config.ts |
HttpClient、ルーター、グローバル サービス |
| モジュール | 遅延ロードされたモジュール | 配列 providers 形の |
機能固有のサービス |
| 要素 | メンバーとその子供たち | 配列 providers コンポーネントの |
ローカル状態、複数のインスタンス |
依存関係の解決は正確なパスに従います: Angular はインジェクターから開始します
依存関係を必要とするコンポーネントの名前を指定し、プロバイダーが見つかるまでツリーを上に進みます。
対応しています。プロバイダーが見つからない場合は、エラーが生成されます
NullInjectorError (依存関係がオプションとしてマークされている場合を除く)。
// Un servizio che gestisce lo stato locale
@Injectable()
export class FormStateService {
private fields = signal<Map<string, any>>(new Map());
private dirty = signal(false);
setValue(key: string, value: any) {
this.fields.update(m => {
const newMap = new Map(m);
newMap.set(key, value);
return newMap;
});
this.dirty.set(true);
}
isDirty = this.dirty.asReadonly();
}
// Ogni istanza del componente riceve il proprio FormStateService
@Component({
selector: 'app-editable-card',
standalone: true,
providers: [FormStateService], // Istanza LOCALE
template: `
<div class="card" [class.dirty]="formState.isDirty()">
<ng-content />
</div>
`
})
export class EditableCardComponent {
protected formState = inject(FormStateService);
}
4. 提供されるオプション
デコレータ @Injectable() オプションを受け入れる providedIn それ
サービスがどのインジェクターに自動的に登録されるかを決定します。このオプション
それは基本的なものです 木が揺れる: Angular は非サービスを削除できます
最終バンドルによって使用されます。
利用可能なオプションが提供されます
| 価値 | ほうき | ツリーシェイク可能 | 使用事例 |
|---|---|---|---|
'root' |
アプリケーション全体のシングルトン | Si | グローバル サービス (認証、API、状態) |
'platform' |
Angular アプリケーション間で共有される | Si | マイクロフロントエンド、要素 |
'any' |
遅延ロードモジュールごとに 1 つのインスタンス | Si | 遅延機能の分離状態 |
| 成分 | コンポーネントごとのインスタンス | No | ローカル状態、フォーム、ダイアログ |
// Singleton globale - l'approccio più comune
@Injectable({ providedIn: 'root' })
export class AuthService {
private currentUser = signal<User | null>(null);
isAuthenticated = computed(() => this.currentUser() !== null);
}
// Condiviso tra app Angular sulla stessa pagina
@Injectable({ providedIn: 'platform' })
export class SharedThemeService {
theme = signal<'light' | 'dark'>('dark');
}
// Un'istanza per ogni modulo lazy-loaded
@Injectable({ providedIn: 'any' })
export class FeatureAnalyticsService {
private events: AnalyticsEvent[] = [];
// Ogni feature lazy ha il proprio tracker indipendente
}
// Istanza per componente (non tree-shakable)
@Injectable() // Nessun providedIn
export class DialogStateService {
isOpen = signal(false);
data = signal<any>(null);
}
// Va registrato nell'array providers del componente:
@Component({
providers: [DialogStateService]
})
export class MyDialogComponent {
state = inject(DialogStateService);
}
providedIn: 'any' に注意してください
オプション 'any' 遅延ロードされるモジュールごとに個別のインスタンスを作成します。
シングルトンを期待している場合、これにより予期しない動作が発生する可能性があります。ただ使ってください
機能モジュール間の状態を明示的に分離したい場合。建築とともに
スタンドアロンおよび遅延ルート、 'any' 必要になることはほとんどありません。
あなたが好む 'root' シングルトンまたは私用 providers コンポーネントの
地元の州のために。
5. マルチプロバイダー: MULTI パターン
パターン マルチプロバイダー 複数の値を同じ値に関連付けることができます インジェクショントークン。トークンをリクエストすると、Angular は 配列 すべての値が記録されています。このパターンは、次のような重要な機能の基礎となります。 HTTP インターセプター、ルーティング ガード、およびアプリケーション初期化子。
import { InjectionToken, inject } from '@angular/core';
// Token multi-provider per validatori custom
export const FORM_VALIDATORS = new InjectionToken<FormValidator[]>(
'FORM_VALIDATORS'
);
export interface FormValidator {
name: string;
validate(value: any): string | null;
}
// Registrazione di più provider con multi: true
export const appConfig: ApplicationConfig = {
providers: [
{
provide: FORM_VALIDATORS,
useValue: {
name: 'required',
validate: (v: any) => v ? null : 'Campo obbligatorio'
},
multi: true
},
{
provide: FORM_VALIDATORS,
useValue: {
name: 'minLength',
validate: (v: string) =>
v?.length >= 3 ? null : 'Minimo 3 caratteri'
},
multi: true
},
{
provide: FORM_VALIDATORS,
useFactory: () => {
const http = inject(HttpClient);
return {
name: 'uniqueEmail',
validate: async (v: string) => {
// Validazione asincrona via API
return null;
}
};
},
multi: true
}
]
};
// Consumo nel servizio
@Injectable({ providedIn: 'root' })
export class ValidationService {
private validators = inject(FORM_VALIDATORS);
validateAll(value: any): string[] {
return this.validators
.map(v => v.validate(value))
.filter((err): err is string => err !== null);
}
}
Angular に統合されたマルチプロバイダー
HTTP_INTERCEPTORS- HTTP インターセプター (従来のクラスベースのアプローチ)APP_INITIALIZER- アプリのブートストラップ前に実行される機能ENVIRONMENT_INITIALIZER- 環境インジェクターのイニシャライザーNG_VALIDATORS/NG_ASYNC_VALIDATORS- フォームバリデーターROUTES- 動的ルート構成
6. ツリーシェイク可能なプロバイダー
Angular における最新の DI の主な利点の 1 つは、
木が揺れる プロバイダーの。サービスを登録すると、
providedIn: 'root' デコレーターのファクトリーとバンドラーは、
どのコンポーネントもインポートしない場合は、最終ビルドから完全に削除してください。
// TREE-SHAKABLE: il servizio si auto-registra
// Se nessun componente/servizio lo importa, viene eliminato dal bundle
@Injectable({ providedIn: 'root' })
export class AnalyticsService {
track(event: string) {
// Se AnalyticsService non e importato da nessuna parte,
// tutto questo codice viene rimosso dal bundle
console.log('Track:', event);
}
}
// TREE-SHAKABLE con factory: dipendenze calcolate
export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>(
'FEATURE_FLAGS',
{
providedIn: 'root',
factory: () => {
const http = inject(HttpClient);
// La factory viene eliminata se FEATURE_FLAGS
// non viene mai iniettato
return { darkMode: true, newDashboard: false };
}
}
);
// NON TREE-SHAKABLE: registrato nell'array providers
// Sempre incluso nel bundle, anche se non usato
export const appConfig: ApplicationConfig = {
providers: [
LegacyService, // Sempre nel bundle!
{ provide: LoggerService, useClass: ConsoleLoggerService }
]
};
バンドルに対するツリーシェイクの影響
| アプローチ | 登録 | ツリーシェイク可能 | 使用しない場合はバンドルに含まれます |
|---|---|---|---|
providedIn: 'root' |
サービスデコレーターで | Si | いいえ(削除されました) |
InjectionToken 工場あり |
トークンコンストラクター内 | Si | いいえ(削除されました) |
配列 providers |
app.config.ts またはコンポーネント内 | No | はい (常に存在します) |
NgModule providers |
フォームで | No | はい (常に存在します) |
ツリーシェイキングのベストプラクティス
- 常に使用する
providedIn: 'root'グローバルサービス向け - プロバイダー配列ではなく、トークン自体でファクトリを定義します。
- アレイへのサービスの登録を避ける
providersdiapp.config.ts必要な場合を除く (オーバーライド、マルチプロバイダー) - バンドル サイズを監視する
ng build --stats-jsonewebpack-bundle-analyzer
7. 機能的なガードとインターセプター
Angular 15 以降、ガードとインターセプターはインターフェイスを持つクラスから進化しました。
ある 単純な関数。この機能的なアプローチにより定型文が削減され、
構成可能性を向上させ、機能をネイティブに活用します inject().
7.1 機能的なガード
// PRIMA: Guard come classe (deprecato)
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private auth: AuthService,
private router: Router
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean | UrlTree {
if (this.auth.isAuthenticated()) {
return true;
}
return this.router.createUrlTree(['/login']);
}
}
// DOPO: Guard come funzione
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isAuthenticated()) {
return true;
}
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
};
// Guard con parametri: factory function
export function roleGuard(requiredRole: string): CanActivateFn {
return (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.hasRole(requiredRole)) {
return true;
}
return router.createUrlTree(['/forbidden']);
};
}
// Configurazione delle rotte
export const routes: Routes = [
{
path: 'dashboard',
canActivate: [authGuard],
loadComponent: () => import('./dashboard/dashboard.component')
.then(m => m.DashboardComponent)
},
{
path: 'admin',
canActivate: [authGuard, roleGuard('admin')],
loadComponent: () => import('./admin/admin.component')
.then(m => m.AdminComponent)
}
];
7.2 機能的インターセプター
import { HttpInterceptorFn } from '@angular/common/http';
// Interceptor per autenticazione
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const auth = inject(AuthService);
const token = auth.getToken();
if (token) {
const cloned = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next(cloned);
}
return next(req);
};
// Interceptor per logging
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
const startTime = Date.now();
return next(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
const duration = Date.now() - startTime;
console.log(`${req.method} ${req.url} - ${duration}ms`);
}
})
);
};
// Interceptor per retry con backoff
export const retryInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
retry({
count: 3,
delay: (error, retryCount) => {
if (error.status === 429 || error.status >= 500) {
return timer(Math.pow(2, retryCount) * 1000);
}
throw error;
}
})
);
};
// Registrazione in app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([
authInterceptor,
loggingInterceptor,
retryInterceptor
])
)
]
};
クラスベースのインターセプターと関数型インターセプターの比較
| 待ってます | クラスベース (レガシー) | 機能的(モダン) |
|---|---|---|
| 構文 | クラス + インターフェース HttpInterceptor |
シンプルな機能 HttpInterceptorFn |
| 登録 | HTTP_INTERCEPTORS マルチプロバイダー |
withInterceptors([]) |
| DI | ビルダー | inject() |
| 構成 | パラメータ化が難しい | パラメーターのファクトリー関数 |
| 木が揺れる | 限定 | 最適 |
8.EnvironmentInjector と動的コンポーネント
L'EnvironmentInjector Angular 14 で導入されたインジェクターの一種です。
レベルではなく「環境」レベル (ルート、ルート、遅延モジュール) で動作します。
DOM 要素。これはコンポーネントを動的に作成する場合に重要です。
createComponent() o ViewContainerRef、それが可能にするので、
正しいインジェクションコンテキストにアクセスするためのコンポーネントを作成しました。
import {
Component,
EnvironmentInjector,
createEnvironmentInjector,
createComponent,
ViewContainerRef,
inject
} from '@angular/core';
// Servizio per creare componenti con un contesto DI personalizzato
@Injectable({ providedIn: 'root' })
export class DynamicComponentService {
private parentInjector = inject(EnvironmentInjector);
createIsolatedComponent<T>(
component: Type<T>,
vcr: ViewContainerRef,
providers: StaticProvider[] = []
) {
// Crea un nuovo EnvironmentInjector con provider aggiuntivi
const childInjector = createEnvironmentInjector(
[
{ provide: DIALOG_DATA, useValue: { title: 'My Dialog' } },
...providers
],
this.parentInjector
);
// Crea il componente nel nuovo contesto
const componentRef = createComponent(component, {
environmentInjector: childInjector,
hostElement: document.createElement('div')
});
// Inserisci nella view
vcr.insert(componentRef.hostView);
// Importante: distruggi l'injector quando il componente viene distrutto
componentRef.onDestroy(() => childInjector.destroy());
return componentRef;
}
}
// Uso in un componente host
@Component({
selector: 'app-dialog-host',
standalone: true,
template: `<ng-container #outlet />`
})
export class DialogHostComponent {
private dynamicService = inject(DynamicComponentService);
private vcr = inject(ViewContainerRef);
openDialog(dialogComponent: Type<any>, data: any) {
this.dynamicService.createIsolatedComponent(
dialogComponent,
this.vcr,
[{ provide: DIALOG_DATA, useValue: data }]
);
}
}
EnvironmentInjector を使用する場合
- 動的ダイアログ/モーダル: データ注入が必要な実行時に作成されるコンポーネント
- プラグインシステム: 分離されたプロバイダーによるコンポーネントの動的ロード
- マイクロフロントエンド: 各マイクロアプリには独自のインジェクションコンテキストがあります
- 高度なテスト: テスト用に分離されたインジェクションコンテキストを作成する
9. runInInjectionContext: どこでもインジェクション
機能 runInInjectionContext()Angular 16 で導入されたことで、次のことが可能になります。
インジェクションコンテキスト内で任意のコードを実行します。これで一つ解決です
の限界のうち inject(): 期間中のみ彼女に電話をかけることができます。
建設段階。と runInInjectionContext()、任意の関数
依存関係にアクセスできます。
import {
runInInjectionContext,
EnvironmentInjector,
inject,
DestroyRef
} from '@angular/core';
// Funzione utility che richiede inject()
function createAutoSaveEffect(formId: string) {
const http = inject(HttpClient);
const destroyRef = inject(DestroyRef);
const intervalId = setInterval(() => {
const formData = getFormData(formId);
http.post('/api/autosave', formData).subscribe();
}, 30000);
// Cleanup automatico quando il contesto viene distrutto
destroyRef.onDestroy(() => clearInterval(intervalId));
}
// Uso nel componente: funziona nel costruttore (contesto attivo)
@Component({ ... })
export class EditorComponent {
constructor() {
createAutoSaveEffect('editor-form');
}
}
// Uso al di fuori del costruttore: runInInjectionContext
@Component({ ... })
export class DashboardComponent {
private envInjector = inject(EnvironmentInjector);
onTabSelected(tabId: string) {
// Questo metodo viene chiamato da un click handler,
// quindi inject() non funzionerebbe direttamente
runInInjectionContext(this.envInjector, () => {
createAutoSaveEffect(tabId);
});
}
}
// Pattern avanzato: creare un "service locator" basato su funzioni
export function createServiceAccessor<T>(token: ProviderToken<T>) {
let cachedInstance: T | undefined;
return {
init: () => {
cachedInstance = inject(token);
},
get: () => {
if (!cachedInstance) {
throw new Error('Service not initialized. Call init() first.');
}
return cachedInstance;
}
};
}
runInInjectionContext の使用には注意してください
パワフルではありますが、 runInInjectionContext() 過度に使用すべきではありません。
挿入されたコードを外部で実行する具体的な理由がある場合に使用します。
建設段階の。頻繁に使用していることに気付いた場合は、おそらく
コンポーネントの設計を改善することができます。インジェクターが通過したことも覚えておいてください
でなければなりません EnvironmentInjector (ではありません ElementInjector)、
したがって、コンポーネント レベルで登録されたプロバイダーにはアクセスできなくなります。
10. 依存性注入テスト
DI の大きな利点の 1 つはテストのしやすさです。 Angular は、次のような強力なツールを提供します。
単体テストで依存関係を構成、置換、検証します。アプローチとは
現代に基づいて inject() およびスタンドアロン コンポーネント、テスト セットアップ
大幅に簡単になりました。
import { TestBed } from '@angular/core/testing';
describe('UserService', () => {
let service: UserService;
let httpMock: jest.Mocked<HttpClient>;
beforeEach(() => {
// Mock dell'HttpClient
const mockHttp = {
get: jest.fn(),
post: jest.fn()
};
TestBed.configureTestingModule({
providers: [
UserService,
{ provide: HttpClient, useValue: mockHttp },
{ provide: API_BASE_URL, useValue: 'http://test-api.com' }
]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpClient) as jest.Mocked<HttpClient>;
});
it('should fetch users from the API', () => {
const mockUsers = [{ id: 1, name: 'Alice' }];
httpMock.get.mockReturnValue(of(mockUsers));
service.getUsers().subscribe(users => {
expect(users).toEqual(mockUsers);
expect(httpMock.get).toHaveBeenCalledWith(
'http://test-api.com/users'
);
});
});
});
// Test di un componente standalone con override
describe('DashboardComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DashboardComponent],
})
.overrideComponent(DashboardComponent, {
set: {
providers: [
{
provide: AnalyticsService,
useValue: { track: jest.fn() }
}
]
}
})
.compileComponents();
});
it('should track page view on init', () => {
const fixture = TestBed.createComponent(DashboardComponent);
const analytics = fixture.debugElement.injector.get(AnalyticsService);
fixture.detectChanges();
expect(analytics.track).toHaveBeenCalledWith('dashboard_view');
});
});
// Test di guard funzionali
describe('authGuard', () => {
it('should redirect to login when not authenticated', () => {
TestBed.configureTestingModule({
providers: [
{
provide: AuthService,
useValue: { isAuthenticated: () => false }
},
provideRouter([])
]
});
const injector = TestBed.inject(EnvironmentInjector);
// Esegui il guard nel contesto di injection
const result = runInInjectionContext(injector, () =>
authGuard(
{} as ActivatedRouteSnapshot,
{ url: '/dashboard' } as RouterStateSnapshot
)
);
expect(result).toBeInstanceOf(UrlTree);
});
it('should allow access when authenticated', () => {
TestBed.configureTestingModule({
providers: [
{
provide: AuthService,
useValue: { isAuthenticated: () => true }
},
provideRouter([])
]
});
const injector = TestBed.inject(EnvironmentInjector);
const result = runInInjectionContext(injector, () =>
authGuard(
{} as ActivatedRouteSnapshot,
{ url: '/dashboard' } as RouterStateSnapshot
)
);
expect(result).toBe(true);
});
});
DIのテストパターン
| シナリオ | 方法 | Esempio |
|---|---|---|
| サービスを置き換える | provide + useValue |
{ provide: AuthService, useValue: mockAuth } |
| コンポーネントレベルの上書き | overrideComponent |
ローカルコンポーネントプロバイダー |
| テストインジェクショントークン | provide + useValue |
{ provide: API_URL, useValue: 'http://test' } |
| 機能的ガードをテストする | runInInjectionContext |
TestBed コンテキストでガードを実行する |
| 実際のメソッドをスパイする | inject + spyOn |
spyOn(TestBed.inject(Svc), 'method') |
DI テストのベスト プラクティス
- 手動モックを好む a
jasmine.createSpyObj型をより適切に制御するため - サービスを個別にテストします。 すべての依存関係をモックし、サービス ロジックをテストするだけです
- アメリカ合衆国
TestBed.inject()テストモジュールに登録されているサービスにアクセスするため - 機能的なガードとインターセプターの場合:
runInInjectionContext()TestBed インジェクターを使用した場合 - エラーを確認します。 それを頭
NullInjectorError必須プロバイダーが欠落している場合に生成されます
概要と次のステップ
この記事では、Angular での高度な依存関係の挿入について説明しました。
新しいAPIから inject() テストまで。 Angular e の最新の DI
単純なオブジェクト作成メカニズムをはるかに超えた、洗練されたシステムです。
これはアプリケーションのアーキテクチャ、パフォーマンス、保守性に影響します。
主要な概念
- 機能
inject()コンストラクター インジェクションを置き換え、可読性、再利用、ツリーシェイキングを向上させます。 InjectionToken強力な型指定とファクトリ ツリーシェイキングを使用して非クラス値を注入できます- インジェクターの階層 (プラットフォーム、ルート、モジュール、要素) によって、依存関係の範囲と有効期間が決まります。
providedIn: 'root'ツリーシェイク可能なシングルトンサービスの推奨オプション- パターン
multi: trueこれはインターセプタ、ガード、イニシャライザの基礎です - 機能的なガード (
CanActivateFn) および機能的インターセプター (HttpInterceptorFn) ボイラープレートを削減し、堆肥化可能性を向上させます。 EnvironmentInjectorecreateEnvironmentInjector()分離された DI コンテキストを使用してコンポーネントを動的に作成できるようにするrunInInjectionContext()使用できるようにしますinject()建設段階以外では- DI テストのエクスプロイト
TestBed,overrideComponenterunInInjectionContext完全なモックと検証用
シリーズの次の最終記事では、 Angular 17 から 21 への移行、 このシリーズで得たすべての知識を移行に適用する方法を見ていきます。 既存のアプリケーションを Angular の最新 API に変換するためのステップバイステップ ガイド 移行のあらゆる側面をカバーします。







