正在加载,请稍候…

Dependency Injection in TypeScript: Manual and Container-Based

Implement dependency injection in TypeScript using manual DI, InversifyJS, and NestJS. Understand IoC containers, decorators, and how DI improves testability.

Dependency Injection in TypeScript

DI inverts control of creating dependencies, making code more testable and modular.

Manual DI (Composition Root)

// Define interfaces
interface UserRepository {
  findById(id: string): Promise<User | null>;
  save(user: User): Promise<User>;
}

interface EmailService {
  sendWelcome(email: string): Promise<void>;
}

// Implementations
class PostgresUserRepository implements UserRepository { /* ... */ }
class SendgridEmailService implements EmailService { /* ... */ }
class MockEmailService implements EmailService {
  sentEmails: string[] = [];
  async sendWelcome(email: string) { this.sentEmails.push(email); }
}

// Service with injected dependencies
class UserService {
  constructor(
    private readonly userRepo: UserRepository,
    private readonly emailService: EmailService
  ) {}

  async register(email: string, password: string): Promise<User> {
    const user = await this.userRepo.save(User.create(email, password));
    await this.emailService.sendWelcome(email);
    return user;
  }
}

// Composition root (app startup)
const userRepo = new PostgresUserRepository(db);
const emailService = new SendgridEmailService(apiKey);
const userService = new UserService(userRepo, emailService);

// In tests
const mockEmail = new MockEmailService();
const testUserService = new UserService(new InMemoryUserRepository(), mockEmail);

InversifyJS Container

import { Container, injectable, inject } from 'inversify';

const TYPES = {
  UserRepository: Symbol('UserRepository'),
  EmailService: Symbol('EmailService'),
  UserService: Symbol('UserService'),
};

@injectable()
class PostgresUserRepository implements UserRepository { /* ... */ }

@injectable()
class SendgridEmailService implements EmailService { /* ... */ }

@injectable()
class UserService {
  constructor(
    @inject(TYPES.UserRepository) private userRepo: UserRepository,
    @inject(TYPES.EmailService) private emailService: EmailService
  ) {}
}

// Configure container
const container = new Container();
container.bind<UserRepository>(TYPES.UserRepository).to(PostgresUserRepository);
container.bind<EmailService>(TYPES.EmailService).to(SendgridEmailService);
container.bind<UserService>(TYPES.UserService).to(UserService);

// Resolve
const userService = container.get<UserService>(TYPES.UserService);

NestJS DI

// service.ts
@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private userRepo: Repository<User>,
    private emailService: EmailService,
    private configService: ConfigService
  ) {}

  async create(dto: CreateUserDto): Promise<User> {
    const user = this.userRepo.create(dto);
    const saved = await this.userRepo.save(user);
    await this.emailService.sendWelcome(saved.email);
    return saved;
  }
}

// module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UserService, EmailService],
  exports: [UserService],
})
export class UserModule {}

// Testing with NestJS
describe('UserService', () => {
  let service: UserService;
  let mockEmailService: jest.Mocked<EmailService>;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: EmailService,
          useValue: { sendWelcome: jest.fn() },
        },
        {
          provide: getRepositoryToken(User),
          useValue: { create: jest.fn(), save: jest.fn() },
        },
      ],
    }).compile();

    service = module.get<UserService>(UserService);
    mockEmailService = module.get(EmailService);
  });
});

Scopes

@Injectable({ scope: Scope.DEFAULT })   // Singleton (default)
class DatabaseConnection {}

@Injectable({ scope: Scope.REQUEST })   // New instance per request
class RequestContextService {}

@Injectable({ scope: Scope.TRANSIENT }) // New instance per injection
class TaskRunner {}

DI containers shine in large applications with complex dependency graphs.