Angular依赖注入中useFactory()的判断上下文来源及示例问询
useFactory() Conditionals in Angular Great question—this is a common point of confusion when working with Angular's dependency injection system! The key thing to remember is that useFactory() runs during the DI container initialization phase, not when the component is created. That means your conditional logic can't rely on component state (since components don't exist yet), but it can access a range of DI-aware or static context sources. Let's walk through the most common ones with concrete examples.
1. Static Environment Variables
Angular's built-in environment files are perfect for static configuration flags that don't change at runtime. You can reference these directly in your factory function.
Example:
First, define a flag in your environment.ts:
// environment.ts export const environment = { production: false, useMockData: true // Flag to toggle mock vs real service };
Then use it in your module's provider configuration:
import { NgModule } from '@angular/core'; import { environment } from '../environments/environment'; import { DataService } from './data.service'; import { MockDataService } from './mock-data.service'; @NgModule({ providers: [ { provide: DataService, useFactory: () => { // Context comes from static environment variable if (environment.useMockData) { return new MockDataService(); } else { return new DataService(); } } } ] }) export class AppModule {}
2. Injected Dependencies (Pre-Initialized Services)
You can inject other services that are already available in the DI container into your factory function. This is super useful for runtime-loaded configuration (like settings fetched from an API).
Example: Using a Config Service
First, create a service to hold your runtime config:
// config.service.ts import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class ConfigService { apiConfig: { useLegacyApi: boolean } | null = null; loadConfig(): Promise<void> { // Simulate fetching config from API return fetch('/api/config') .then(res => res.json()) .then(config => { this.apiConfig = config; }); } }
Next, use APP_INITIALIZER to load the config before the app initializes:
// app.module.ts import { NgModule, APP_INITIALIZER } from '@angular/core'; import { ConfigService } from './config.service'; import { LegacyApiService } from './legacy-api.service'; import { ModernApiService } from './modern-api.service'; import { ApiService } from './api.service'; // Factory to load config on app start export function initConfig(configService: ConfigService) { return () => configService.loadConfig(); } @NgModule({ providers: [ { provide: APP_INITIALIZER, useFactory: initConfig, deps: [ConfigService], multi: true }, { provide: ApiService, useFactory: (configService: ConfigService) => { // Context comes from the injected ConfigService (pre-loaded) if (configService.apiConfig?.useLegacyApi) { return new LegacyApiService(); } else { return new ModernApiService(); } }, deps: [ConfigService] // Declare dependencies for the factory } ] }) export class AppModule {}
3. Platform Information
You can check whether the app is running in the browser or on the server (for SSR) using Angular's PLATFORM_ID token.
Example:
import { NgModule, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser, isPlatformServer } from '@angular/common'; import { StorageService } from './storage.service'; import { ServerStorageService } from './server-storage.service'; @NgModule({ providers: [ { provide: StorageService, useFactory: (platformId: object) => { // Context comes from the PLATFORM_ID token if (isPlatformBrowser(platformId)) { return new StorageService(); // Uses localStorage } else if (isPlatformServer(platformId)) { return new ServerStorageService(); // Uses server-side storage } throw new Error('Unsupported platform'); }, deps: [PLATFORM_ID] } ] }) export class CoreModule {}
4. Global Constants/Static Configuration Objects
You can also define static configuration objects outside of Angular's DI system and reference them directly in your factory.
Example:
// app-config.ts export const AppConfig = { features: { enableNewDashboard: true } }; // dashboard.module.ts import { NgModule } from '@angular/core'; import { AppConfig } from '../app-config'; import { NewDashboardComponent } from './new-dashboard.component'; import { LegacyDashboardComponent } from './legacy-dashboard.component'; import { DashboardComponent } from './dashboard.component'; @NgModule({ providers: [ { provide: DashboardComponent, useFactory: () => { // Context comes from a static global config if (AppConfig.features.enableNewDashboard) { return new NewDashboardComponent(); } else { return new LegacyDashboardComponent(); } } } ] }) export class DashboardModule {}
Key Takeaway
All these context sources are available before components are created because they're either:
- Static (environment vars, global constants)
- Initialized early via
APP_INITIALIZER - Provided by Angular's core DI system (like
PLATFORM_ID)
This lets you make dependency decisions at the DI level, ensuring the right service is injected wherever it's needed—without relying on component state.
内容的提问来源于stack exchange,提问作者Ole

