angular-dependency-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Angular Dependency Injection

Angular 依赖注入

Master Angular's dependency injection system for building modular, testable applications with proper service architecture.
掌握Angular的依赖注入系统,构建具备合理服务架构的模块化、可测试应用程序。

DI Fundamentals

依赖注入基础

Angular's DI is hierarchical and uses decorators and providers:
typescript
import { Injectable } from '@angular/core';

// Service injectable at root level
@Injectable({
  providedIn: 'root'
})
export class UserService {
  private users: User[] = [];

  getUsers(): User[] {
    return this.users;
  }

  addUser(user: User): void {
    this.users.push(user);
  }
}

// Component injection
import { Component } from '@angular/core';

@Component({
  selector: 'app-user-list',
  template: `
    <div *ngFor="let user of users">
      {{ user.name }}
    </div>
  `
})
export class UserListComponent {
  users: User[];

  constructor(private userService: UserService) {
    this.users = this.userService.getUsers();
  }
}
Angular的DI是层级化的,它使用装饰器和提供者:
typescript
import { Injectable } from '@angular/core';

// Service injectable at root level
@Injectable({
  providedIn: 'root'
})
export class UserService {
  private users: User[] = [];

  getUsers(): User[] {
    return this.users;
  }

  addUser(user: User): void {
    this.users.push(user);
  }
}

// Component injection
import { Component } from '@angular/core';

@Component({
  selector: 'app-user-list',
  template: `
    <div *ngFor="let user of users">
      {{ user.name }}
    </div>
  `
})
export class UserListComponent {
  users: User[];

  constructor(private userService: UserService) {
    this.users = this.userService.getUsers();
  }
}

Provider Types

提供者类型

useClass - Class Provider

useClass - 类提供者

typescript
import { Injectable, Provider } from '@angular/core';

// Interface
interface Logger {
  log(message: string): void;
}

// Implementations
@Injectable()
export class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(message);
  }
}

@Injectable()
export class FileLogger implements Logger {
  log(message: string): void {
    // Write to file
  }
}

// Provider configuration
const loggerProvider: Provider = {
  provide: Logger,
  useClass: ConsoleLogger // or FileLogger based on env
};

// In module
@NgModule({
  providers: [loggerProvider]
})
export class AppModule {}

// Usage
export class MyComponent {
  constructor(private logger: Logger) {
    this.logger.log('Component initialized');
  }
}
typescript
import { Injectable, Provider } from '@angular/core';

// Interface
interface Logger {
  log(message: string): void;
}

// Implementations
@Injectable()
export class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(message);
  }
}

@Injectable()
export class FileLogger implements Logger {
  log(message: string): void {
    // Write to file
  }
}

// Provider configuration
const loggerProvider: Provider = {
  provide: Logger,
  useClass: ConsoleLogger // or FileLogger based on env
};

// In module
@NgModule({
  providers: [loggerProvider]
})
export class AppModule {}

// Usage
export class MyComponent {
  constructor(private logger: Logger) {
    this.logger.log('Component initialized');
  }
}

useValue - Value Provider

useValue - 值提供者

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

// Configuration object
export interface AppConfig {
  apiUrl: string;
  timeout: number;
  retries: number;
}

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

// Provider
const configProvider: Provider = {
  provide: APP_CONFIG,
  useValue: {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retries: 3
  }
};

// Module
@NgModule({
  providers: [configProvider]
})
export class AppModule {}

// Usage
export class ApiService {
  constructor(@Inject(APP_CONFIG) private config: AppConfig) {
    console.log(this.config.apiUrl);
  }
}
typescript
import { InjectionToken } from '@angular/core';

// Configuration object
export interface AppConfig {
  apiUrl: string;
  timeout: number;
  retries: number;
}

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

// Provider
const configProvider: Provider = {
  provide: APP_CONFIG,
  useValue: {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retries: 3
  }
};

// Module
@NgModule({
  providers: [configProvider]
})
export class AppModule {}

// Usage
export class ApiService {
  constructor(@Inject(APP_CONFIG) private config: AppConfig) {
    console.log(this.config.apiUrl);
  }
}

useFactory - Factory Provider

useFactory - 工厂提供者

typescript
import { Injectable, InjectionToken } from '@angular/core';

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

// Factory function
export function apiUrlFactory(config: AppConfig): string {
  return config.production
    ? 'https://api.prod.example.com'
    : 'https://api.dev.example.com';
}

// Provider
const apiUrlProvider: Provider = {
  provide: API_URL,
  useFactory: apiUrlFactory,
  deps: [AppConfig] // Dependencies for factory
};

// Complex factory with multiple deps
export function httpClientFactory(
  handler: HttpHandler,
  config: AppConfig,
  logger: Logger
): HttpClient {
  logger.log('Creating HTTP client');
  return new HttpClient(handler);
}

const httpClientProvider: Provider = {
  provide: HttpClient,
  useFactory: httpClientFactory,
  deps: [HttpHandler, AppConfig, Logger]
};
typescript
import { Injectable, InjectionToken } from '@angular/core';

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

// Factory function
export function apiUrlFactory(config: AppConfig): string {
  return config.production
    ? 'https://api.prod.example.com'
    : 'https://api.dev.example.com';
}

// Provider
const apiUrlProvider: Provider = {
  provide: API_URL,
  useFactory: apiUrlFactory,
  deps: [AppConfig] // Dependencies for factory
};

// Complex factory with multiple deps
export function httpClientFactory(
  handler: HttpHandler,
  config: AppConfig,
  logger: Logger
): HttpClient {
  logger.log('Creating HTTP client');
  return new HttpClient(handler);
}

const httpClientProvider: Provider = {
  provide: HttpClient,
  useFactory: httpClientFactory,
  deps: [HttpHandler, AppConfig, Logger]
};

useExisting - Alias Provider

useExisting - 别名提供者

typescript
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class NewLogger {
  log(message: string): void {
    console.log('[NEW]', message);
  }
}

// Alias old logger to new logger
const oldLoggerProvider: Provider = {
  provide: 'OldLogger',
  useExisting: NewLogger
};

// Usage
export class MyComponent {
  constructor(
    @Inject('OldLogger') private logger: NewLogger
  ) {
    this.logger.log('Using aliased logger');
  }
}
typescript
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class NewLogger {
  log(message: string): void {
    console.log('[NEW]', message);
  }
}

// Alias old logger to new logger
const oldLoggerProvider: Provider = {
  provide: 'OldLogger',
  useExisting: NewLogger
};

// Usage
export class MyComponent {
  constructor(
    @Inject('OldLogger') private logger: NewLogger
  ) {
    this.logger.log('Using aliased logger');
  }
}

Injection Tokens

注入令牌

InjectionToken - Type-Safe Tokens

InjectionToken - 类型安全令牌

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

// Primitive token
export const MAX_RETRIES = new InjectionToken<number>('max.retries', {
  providedIn: 'root',
  factory: () => 3 // Default value
});

// Object token
export interface FeatureFlags {
  enableNewUI: boolean;
  enableBeta: boolean;
}

export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>(
  'feature.flags',
  {
    providedIn: 'root',
    factory: () => ({
      enableNewUI: false,
      enableBeta: false
    })
  }
);

// Usage
@Injectable()
export class ApiService {
  constructor(
    @Inject(MAX_RETRIES) private maxRetries: number,
    @Inject(FEATURE_FLAGS) private flags: FeatureFlags
  ) {}
}
typescript
import { InjectionToken } from '@angular/core';

// Primitive token
export const MAX_RETRIES = new InjectionToken<number>('max.retries', {
  providedIn: 'root',
  factory: () => 3 // Default value
});

// Object token
export interface FeatureFlags {
  enableNewUI: boolean;
  enableBeta: boolean;
}

export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>(
  'feature.flags',
  {
    providedIn: 'root',
    factory: () => ({
      enableNewUI: false,
      enableBeta: false
    })
  }
);

// Usage
@Injectable()
export class ApiService {
  constructor(
    @Inject(MAX_RETRIES) private maxRetries: number,
    @Inject(FEATURE_FLAGS) private flags: FeatureFlags
  ) {}
}

String Tokens (Legacy)

String Tokens (Legacy)

typescript
// Not type-safe, avoid when possible
const providers: Provider[] = [
  { provide: 'API_URL', useValue: 'https://api.example.com' },
  { provide: 'TIMEOUT', useValue: 5000 }
];

// Usage
export class MyService {
  constructor(
    @Inject('API_URL') private apiUrl: string,
    @Inject('TIMEOUT') private timeout: number
  ) {}
}
typescript
// Not type-safe, avoid when possible
const providers: Provider[] = [
  { provide: 'API_URL', useValue: 'https://api.example.com' },
  { provide: 'TIMEOUT', useValue: 5000 }
];

// Usage
export class MyService {
  constructor(
    @Inject('API_URL') private apiUrl: string,
    @Inject('TIMEOUT') private timeout: number
  ) {}
}

Hierarchical Injectors

层级注入器

Root Injector

根注入器

typescript
// Singleton across entire app
@Injectable({
  providedIn: 'root'
})
export class GlobalService {
  private state = {};
}

// Same instance everywhere
typescript
// Singleton across entire app
@Injectable({
  providedIn: 'root'
})
export class GlobalService {
  private state = {};
}

// Same instance everywhere

Module Injector

模块注入器

typescript
@Injectable()
export class ModuleService {
  // Service specific to module
}

@NgModule({
  providers: [ModuleService]
})
export class FeatureModule {}

// Different instance per module
typescript
@Injectable()
export class ModuleService {
  // Service specific to module
}

@NgModule({
  providers: [ModuleService]
})
export class FeatureModule {}

// Different instance per module

Component Injector

组件注入器

typescript
@Injectable()
export class ComponentService {
  private data = [];
}

@Component({
  selector: 'app-my-component',
  template: '...',
  providers: [ComponentService] // New instance per component
})
export class MyComponent {
  constructor(private service: ComponentService) {}
}

// Each component instance gets its own service instance
typescript
@Injectable()
export class ComponentService {
  private data = [];
}

@Component({
  selector: 'app-my-component',
  template: '...',
  providers: [ComponentService] // New instance per component
})
export class MyComponent {
  constructor(private service: ComponentService) {}
}

// Each component instance gets its own service instance

Element Injector

元素注入器

typescript
@Directive({
  selector: '[appHighlight]',
  providers: [DirectiveService]
})
export class HighlightDirective {
  constructor(private service: DirectiveService) {}
}

// Each directive instance gets its own service
typescript
@Directive({
  selector: '[appHighlight]',
  providers: [DirectiveService]
})
export class HighlightDirective {
  constructor(private service: DirectiveService) {}
}

// Each directive instance gets its own service

ProvidedIn Options

ProvidedIn选项

typescript
// Root - singleton
@Injectable({
  providedIn: 'root'
})
export class RootService {}

// Platform - shared across multiple apps
@Injectable({
  providedIn: 'platform'
})
export class PlatformService {}

// Any - new instance per module
@Injectable({
  providedIn: 'any'
})
export class AnyService {}

// Module - specific module
@Injectable({
  providedIn: FeatureModule
})
export class FeatureService {}
typescript
// Root - singleton
@Injectable({
  providedIn: 'root'
})
export class RootService {}

// Platform - shared across multiple apps
@Injectable({
  providedIn: 'platform'
})
export class PlatformService {}

// Any - new instance per module
@Injectable({
  providedIn: 'any'
})
export class AnyService {}

// Module - specific module
@Injectable({
  providedIn: FeatureModule
})
export class FeatureService {}

Multi-Providers

多提供者

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

// Token for multiple providers
export const HTTP_INTERCEPTORS =
  new InjectionToken<HttpInterceptor[]>('http.interceptors');

// Multiple implementations
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // Add auth header
    return next.handle(req);
  }
}

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // Log request
    return next.handle(req);
  }
}

// Register as multi-providers
const providers: Provider[] = [
  {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true
  },
  {
    provide: HTTP_INTERCEPTORS,
    useClass: LoggingInterceptor,
    multi: true
  }
];

// Inject as array
export class HttpService {
  constructor(
    @Inject(HTTP_INTERCEPTORS) private interceptors: HttpInterceptor[]
  ) {
    // interceptors is array of all registered interceptors
  }
}
typescript
import { InjectionToken } from '@angular/core';

// Token for multiple providers
export const HTTP_INTERCEPTORS =
  new InjectionToken<HttpInterceptor[]>('http.interceptors');

// Multiple implementations
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // Add auth header
    return next.handle(req);
  }
}

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // Log request
    return next.handle(req);
  }
}

// Register as multi-providers
const providers: Provider[] = [
  {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true
  },
  {
    provide: HTTP_INTERCEPTORS,
    useClass: LoggingInterceptor,
    multi: true
  }
];

// Inject as array
export class HttpService {
  constructor(
    @Inject(HTTP_INTERCEPTORS) private interceptors: HttpInterceptor[]
  ) {
    // interceptors is array of all registered interceptors
  }
}

Optional and Self Decorators

Optional与Self装饰器

@Optional - Allow Missing Dependencies

@Optional - 允许依赖缺失

typescript
import { Optional } from '@angular/core';

@Injectable()
export class MyService {
  constructor(
    @Optional() private logger?: Logger
  ) {
    // logger might be undefined
    this.logger?.log('Service created');
  }
}
typescript
import { Optional } from '@angular/core';

@Injectable()
export class MyService {
  constructor(
    @Optional() private logger?: Logger
  ) {
    // logger might be undefined
    this.logger?.log('Service created');
  }
}

@Self - Only Current Injector

@Self - 仅从当前注入器获取

typescript
import { Self } from '@angular/core';

@Component({
  selector: 'app-my-component',
  providers: [LocalService]
})
export class MyComponent {
  constructor(
    @Self() private local: LocalService // Only from this component
  ) {}
}
typescript
import { Self } from '@angular/core';

@Component({
  selector: 'app-my-component',
  providers: [LocalService]
})
export class MyComponent {
  constructor(
    @Self() private local: LocalService // Only from this component
  ) {}
}

@SkipSelf - Skip Current Injector

@SkipSelf - 跳过当前注入器

typescript
import { SkipSelf } from '@angular/core';

@Component({
  selector: 'app-child',
  providers: [SharedService]
})
export class ChildComponent {
  constructor(
    @SkipSelf() private parent: SharedService // From parent, not self
  ) {}
}
typescript
import { SkipSelf } from '@angular/core';

@Component({
  selector: 'app-child',
  providers: [SharedService]
})
export class ChildComponent {
  constructor(
    @SkipSelf() private parent: SharedService // From parent, not self
  ) {}
}

@Host - Host Element Injector

@Host - 从宿主元素注入器获取

typescript
import { Host } from '@angular/core';

@Directive({
  selector: '[appChild]'
})
export class ChildDirective {
  constructor(
    @Host() private parent: ParentComponent // From host component
  ) {}
}
typescript
import { Host } from '@angular/core';

@Directive({
  selector: '[appChild]'
})
export class ChildDirective {
  constructor(
    @Host() private parent: ParentComponent // From host component
  ) {}
}

ForRoot and ForChild Patterns

ForRoot与ForChild模式

typescript
import { NgModule, ModuleWithProviders } from '@angular/core';

@NgModule({})
export class SharedModule {
  // For root module - configures services
  static forRoot(config: SharedConfig): ModuleWithProviders<SharedModule> {
    return {
      ngModule: SharedModule,
      providers: [
        SharedService,
        {
          provide: SHARED_CONFIG,
          useValue: config
        }
      ]
    };
  }

  // For feature modules - no service providers
  static forChild(): ModuleWithProviders<SharedModule> {
    return {
      ngModule: SharedModule,
      providers: [] // No providers, use root services
    };
  }
}

// Usage in AppModule
@NgModule({
  imports: [
    SharedModule.forRoot({ apiUrl: 'https://api.example.com' })
  ]
})
export class AppModule {}

// Usage in feature module
@NgModule({
  imports: [
    SharedModule.forChild()
  ]
})
export class FeatureModule {}
typescript
import { NgModule, ModuleWithProviders } from '@angular/core';

@NgModule({})
export class SharedModule {
  // For root module - configures services
  static forRoot(config: SharedConfig): ModuleWithProviders<SharedModule> {
    return {
      ngModule: SharedModule,
      providers: [
        SharedService,
        {
          provide: SHARED_CONFIG,
          useValue: config
        }
      ]
    };
  }

  // For feature modules - no service providers
  static forChild(): ModuleWithProviders<SharedModule> {
    return {
      ngModule: SharedModule,
      providers: [] // No providers, use root services
    };
  }
}

// Usage in AppModule
@NgModule({
  imports: [
    SharedModule.forRoot({ apiUrl: 'https://api.example.com' })
  ]
})
export class AppModule {}

// Usage in feature module
@NgModule({
  imports: [
    SharedModule.forChild()
  ]
})
export class FeatureModule {}

Tree-Shakable Providers

可摇树优化的提供者

typescript
// Traditional (not tree-shakable)
@Injectable()
export class OldService {}

@NgModule({
  providers: [OldService]
})
export class AppModule {}

// Tree-shakable (preferred)
@Injectable({
  providedIn: 'root'
})
export class NewService {}

// No need to register in module
// Service is removed if never injected
typescript
// Traditional (not tree-shakable)
@Injectable()
export class OldService {}

@NgModule({
  providers: [OldService]
})
export class AppModule {}

// Tree-shakable (preferred)
@Injectable({
  providedIn: 'root'
})
export class NewService {}

// No need to register in module
// Service is removed if never injected

Testing with DI

结合DI进行测试

TestBed Provider Overrides

TestBed提供者覆盖

typescript
import { TestBed } from '@angular/core/testing';

describe('MyComponent', () => {
  let mockUserService: jasmine.SpyObj<UserService>;

  beforeEach(() => {
    mockUserService = jasmine.createSpyObj('UserService', ['getUsers']);

    TestBed.configureTestingModule({
      declarations: [MyComponent],
      providers: [
        { provide: UserService, useValue: mockUserService }
      ]
    });
  });

  it('should get users', () => {
    mockUserService.getUsers.and.returnValue([]);
    const fixture = TestBed.createComponent(MyComponent);
    // Test component with mock
  });
});
typescript
import { TestBed } from '@angular/core/testing';

describe('MyComponent', () => {
  let mockUserService: jasmine.SpyObj<UserService>;

  beforeEach(() => {
    mockUserService = jasmine.createSpyObj('UserService', ['getUsers']);

    TestBed.configureTestingModule({
      declarations: [MyComponent],
      providers: [
        { provide: UserService, useValue: mockUserService }
      ]
    });
  });

  it('should get users', () => {
    mockUserService.getUsers.and.returnValue([]);
    const fixture = TestBed.createComponent(MyComponent);
    // Test component with mock
  });
});

Spy on Dependencies

对依赖进行Spy监听

typescript
import { TestBed } from '@angular/core/testing';

describe('UserService', () => {
  let service: UserService;
  let httpMock: jasmine.SpyObj<HttpClient>;

  beforeEach(() => {
    httpMock = jasmine.createSpyObj('HttpClient', ['get', 'post']);

    TestBed.configureTestingModule({
      providers: [
        UserService,
        { provide: HttpClient, useValue: httpMock }
      ]
    });

    service = TestBed.inject(UserService);
  });

  it('should fetch users', () => {
    httpMock.get.and.returnValue(of([]));
    service.getUsers().subscribe();
    expect(httpMock.get).toHaveBeenCalled();
  });
});
typescript
import { TestBed } from '@angular/core/testing';

describe('UserService', () => {
  let service: UserService;
  let httpMock: jasmine.SpyObj<HttpClient>;

  beforeEach(() => {
    httpMock = jasmine.createSpyObj('HttpClient', ['get', 'post']);

    TestBed.configureTestingModule({
      providers: [
        UserService,
        { provide: HttpClient, useValue: httpMock }
      ]
    });

    service = TestBed.inject(UserService);
  });

  it('should fetch users', () => {
    httpMock.get.and.returnValue(of([]));
    service.getUsers().subscribe();
    expect(httpMock.get).toHaveBeenCalled();
  });
});

When to Use This Skill

何时使用该技能

Use angular-dependency-injection when building modern, production-ready applications that require:
  • Modular service architecture
  • Testable components and services
  • Configuration management
  • Plugin/extension systems
  • Multi-provider patterns (interceptors, validators)
  • Complex service hierarchies
  • Lazy-loaded module isolation
  • Tree-shakable code
在构建现代、生产级应用程序时使用angular-dependency-injection,这些应用需要:
  • 模块化服务架构
  • 可测试的组件与服务
  • 配置管理
  • 插件/扩展系统
  • 多提供者模式(拦截器、验证器)
  • 复杂服务层级
  • 懒加载模块隔离
  • 可摇树优化的代码

Angular DI Best Practices

Angular DI最佳实践

  1. Use
    providedIn: 'root'
    - Tree-shakable and singleton
  2. Use InjectionToken - Type-safe over string tokens
  3. Favor composition - Inject small, focused services
  4. Use factories for complex creation - useFactory for dynamic values
  5. Test with mocks - Override providers in TestBed
  6. Follow forRoot/forChild pattern - For shared modules
  7. Use @Optional sparingly - Prefer defaults or required deps
  8. Document multi-providers - Clear contract for extensions
  9. Avoid circular dependencies - Refactor to common service
  10. Use providedIn module - For module-specific services
  1. 使用
    providedIn: 'root'
    - 支持摇树优化且为单例
  2. 使用InjectionToken - 比字符串令牌更安全
  3. 优先组合模式 - 注入小型、聚焦的服务
  4. 复杂创建逻辑使用工厂 - 用useFactory处理动态值
  5. 使用Mock进行测试 - 在TestBed中覆盖提供者
  6. 遵循forRoot/forChild模式 - 用于共享模块
  7. 谨慎使用@Optional - 优先使用默认值或必填依赖
  8. 记录多提供者 - 明确扩展契约
  9. 避免循环依赖 - 重构为公共服务
  10. 使用providedIn模块 - 针对模块专属服务

DI Pitfalls and Gotchas

DI陷阱与注意事项

  1. Circular dependencies - A depends on B, B depends on A
  2. Providing in component - Creates new instance per component
  3. Missing providers - Runtime error if not provided
  4. Wrong injector level - Service not found in hierarchy
  5. Forgetting multi: true - Overrides instead of adding
  6. String token collisions - Use InjectionToken instead
  7. Not using forRoot - Multiple service instances
  8. Providing eagerly - Use providedIn for tree-shaking
  9. Testing without mocks - Real dependencies in tests
  10. Complex factory deps - Hard to test and maintain
  1. 循环依赖 - A依赖B,B依赖A
  2. 在组件中提供服务 - 每个组件实例都会创建新的服务实例
  3. 缺失提供者 - 运行时会报错
  4. 错误的注入器层级 - 在层级中找不到服务
  5. 忘记设置multi: true - 会覆盖而非添加
  6. 字符串令牌冲突 - 使用InjectionToken替代
  7. 未使用forRoot - 导致多个服务实例
  8. 提前提供服务 - 使用providedIn实现摇树优化
  9. 测试时未使用Mock - 测试中使用真实依赖
  10. 复杂工厂依赖 - 难以测试和维护

Advanced DI Patterns

高级DI模式

Service with Configuration

带配置的服务

typescript
import { Injectable, Inject, InjectionToken } from '@angular/core';

export interface ApiConfig {
  baseUrl: string;
  timeout: number;
}

export const API_CONFIG = new InjectionToken<ApiConfig>('api.config');

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  constructor(@Inject(API_CONFIG) private config: ApiConfig) {}

  get(endpoint: string) {
    return fetch(`${this.config.baseUrl}/${endpoint}`, {
      signal: AbortSignal.timeout(this.config.timeout)
    });
  }
}

// Module
@NgModule({
  providers: [
    {
      provide: API_CONFIG,
      useValue: {
        baseUrl: 'https://api.example.com',
        timeout: 5000
      }
    }
  ]
})
export class AppModule {}
typescript
import { Injectable, Inject, InjectionToken } from '@angular/core';

export interface ApiConfig {
  baseUrl: string;
  timeout: number;
}

export const API_CONFIG = new InjectionToken<ApiConfig>('api.config');

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  constructor(@Inject(API_CONFIG) private config: ApiConfig) {}

  get(endpoint: string) {
    return fetch(`${this.config.baseUrl}/${endpoint}`, {
      signal: AbortSignal.timeout(this.config.timeout)
    });
  }
}

// Module
@NgModule({
  providers: [
    {
      provide: API_CONFIG,
      useValue: {
        baseUrl: 'https://api.example.com',
        timeout: 5000
      }
    }
  ]
})
export class AppModule {}

Abstract Class Provider

抽象类提供者

typescript
import { Injectable } from '@angular/core';

// Abstract class
export abstract class DataService<T> {
  abstract get(id: string): Observable<T>;
  abstract save(item: T): Observable<T>;
}

// Implementation
@Injectable()
export class UserDataService implements DataService<User> {
  get(id: string): Observable<User> {
    // Implementation
  }

  save(user: User): Observable<User> {
    // Implementation
  }
}

// Provider
@NgModule({
  providers: [
    { provide: DataService, useClass: UserDataService }
  ]
})
export class FeatureModule {}

// Usage
export class MyComponent {
  constructor(private dataService: DataService<User>) {}
}
typescript
import { Injectable } from '@angular/core';

// Abstract class
export abstract class DataService<T> {
  abstract get(id: string): Observable<T>;
  abstract save(item: T): Observable<T>;
}

// Implementation
@Injectable()
export class UserDataService implements DataService<User> {
  get(id: string): Observable<User> {
    // Implementation
  }

  save(user: User): Observable<User> {
    // Implementation
  }
}

// Provider
@NgModule({
  providers: [
    { provide: DataService, useClass: UserDataService }
  ]
})
export class FeatureModule {}

// Usage
export class MyComponent {
  constructor(private dataService: DataService<User>) {}
}

Conditional Provider

条件提供者

typescript
import { Injectable, InjectionToken } from '@angular/core';
import { environment } from './environments/environment';

@Injectable()
export class DevLogger {
  log(message: string) {
    console.log('[DEV]', message);
  }
}

@Injectable()
export class ProdLogger {
  log(message: string) {
    // Send to logging service
  }
}

// Factory chooses implementation
export function loggerFactory(): Logger {
  return environment.production
    ? new ProdLogger()
    : new DevLogger();
}

const loggerProvider: Provider = {
  provide: Logger,
  useFactory: loggerFactory
};
typescript
import { Injectable, InjectionToken } from '@angular/core';
import { environment } from './environments/environment';

@Injectable()
export class DevLogger {
  log(message: string) {
    console.log('[DEV]', message);
  }
}

@Injectable()
export class ProdLogger {
  log(message: string) {
    // Send to logging service
  }
}

// Factory chooses implementation
export function loggerFactory(): Logger {
  return environment.production
    ? new ProdLogger()
    : new DevLogger();
}

const loggerProvider: Provider = {
  provide: Logger,
  useFactory: loggerFactory
};

Scope Isolation

作用域隔离

typescript
// Parent service
@Injectable({
  providedIn: 'root'
})
export class GlobalState {
  count = 0;
}

// Child service (isolated)
@Injectable()
export class LocalState {
  count = 0; // Independent per component
}

@Component({
  selector: 'app-counter',
  providers: [LocalState] // New instance per component
})
export class CounterComponent {
  constructor(
    public global: GlobalState,
    public local: LocalState
  ) {}

  incrementGlobal() {
    this.global.count++; // Affects all components
  }

  incrementLocal() {
    this.local.count++; // Only this component
  }
}
typescript
// Parent service
@Injectable({
  providedIn: 'root'
})
export class GlobalState {
  count = 0;
}

// Child service (isolated)
@Injectable()
export class LocalState {
  count = 0; // Independent per component
}

@Component({
  selector: 'app-counter',
  providers: [LocalState] // New instance per component
})
export class CounterComponent {
  constructor(
    public global: GlobalState,
    public local: LocalState
  ) {}

  incrementGlobal() {
    this.global.count++; // Affects all components
  }

  incrementLocal() {
    this.local.count++; // Only this component
  }
}

Resources

相关资源