Exercices — Module 03

Modules & Dependency Injection · 5 exercices pratiques

Ex 01 ⭐ Facile

Module partagé

Créez un LoggerModule avec un LoggerService personnalisé. Exportez le service et importez le module dans deux modules différents (UsersModule et ProductsModule).

Voir la solution
@Injectable()
export class LoggerService {
  private logs: string[] = [];

  log(context: string, message: string) {
    const entry = `[${new Date().toISOString()}] [${context}] ${message}`;
    this.logs.push(entry);
    console.log(entry);
  }

  getLogs() { return [...this.logs]; }
}

@Module({
  providers: [LoggerService],
  exports: [LoggerService],   // Rendu disponible aux autres modules
})
export class LoggerModule {}

@Module({
  imports: [LoggerModule],    // AccĂšs Ă  LoggerService
  providers: [UsersService],
})
export class UsersModule {}
Ex 02 ⭐ Facile

Factory provider asynchrone

Créez un provider 'EXTERNAL_API' avec useFactory asynchrone qui simule la récupération d'une configuration depuis une API externe (avec un délai de 100ms).

Voir la solution
@Module({
  providers: [
    {
      provide: 'EXTERNAL_API',
      useFactory: async (config: ConfigService) => {
        // Simuler un appel API asynchrone
        await new Promise(res => setTimeout(res, 100));
        return {
          baseUrl: config.get('API_URL', 'https://api.example.com'),
          apiKey: config.get('API_KEY'),
          timeout: 5000,
        };
      },
      inject: [ConfigService],
    },
  ],
  exports: ['EXTERNAL_API'],
})
export class ApiModule {}

// Injection dans un service
@Injectable()
export class ApiService {
  constructor(
    @Inject('EXTERNAL_API')
    private readonly apiConfig: { baseUrl: string; apiKey: string; timeout: number },
  ) {}
}
Ex 03 ⭐⭐ Moyen

Module dynamique avec options

CrĂ©ez un ThrottlerModule dynamique avec register(options) permettant de configurer la limite de requĂȘtes par IP : nombre max de requĂȘtes et fenĂȘtre temporelle.

Voir la solution
interface ThrottlerOptions {
  limit: number;
  windowMs: number;
}

@Module({})
export class ThrottlerModule {
  static register(options: ThrottlerOptions): DynamicModule {
    return {
      module: ThrottlerModule,
      providers: [
        { provide: 'THROTTLER_OPTIONS', useValue: options },
        ThrottlerService,
      ],
      exports: [ThrottlerService],
    };
  }
}

// app.module.ts
@Module({
  imports: [
    ThrottlerModule.register({ limit: 100, windowMs: 60 * 1000 }), // 100 req/min
  ],
})
export class AppModule {}
Ex 04 ⭐⭐ Moyen

Module global @Global()

Créez un CoreModule global contenant un AppConfigService et un DateService. Vérifiez que ces services sont disponibles dans UsersService sans import explicite de CoreModule.

Voir la solution
@Injectable()
export class DateService {
  now(): Date { return new Date(); }
  format(date: Date): string { return date.toISOString().split('T')[0]; }
  addDays(date: Date, days: number): Date {
    const d = new Date(date);
    d.setDate(d.getDate() + days);
    return d;
  }
}

@Global()
@Module({
  providers: [AppConfigService, DateService],
  exports: [AppConfigService, DateService],
})
export class CoreModule {}

// AppModule importe CoreModule une seule fois
@Module({ imports: [CoreModule, UsersModule] })
export class AppModule {}

// UsersService peut injecter DateService directement (pas besoin d'importer CoreModule)
@Injectable()
export class UsersService {
  constructor(private readonly dateService: DateService) {}
}
Ex 05 ⭐⭐⭐ Difficile

Résoudre une dépendance circulaire

UsersService dépend de AuthService (pour vérifier les tokens) et AuthService dépend de UsersService (pour trouver les utilisateurs). Résolvez cette dépendance circulaire avec forwardRef().

Voir la solution
// users.service.ts
@Injectable()
export class UsersService {
  constructor(
    @Inject(forwardRef(() => AuthService))
    private readonly authService: AuthService,
    @InjectRepository(User) private userRepo: Repository<User>,
  ) {}

  async findByValidToken(token: string): Promise<User | null> {
    const userId = await this.authService.validateToken(token);
    if (!userId) return null;
    return this.findOne(userId);
  }
}

// auth.service.ts
@Injectable()
export class AuthService {
  constructor(
    @Inject(forwardRef(() => UsersService))
    private readonly usersService: UsersService,
    private readonly jwtService: JwtService,
  ) {}

  async validateUser(email: string, password: string) {
    const user = await this.usersService.findByEmail(email);
    // ...
  }
}

// Modules
@Module({
  imports: [forwardRef(() => AuthModule)],
  exports: [UsersService],
})
export class UsersModule {}

@Module({
  imports: [forwardRef(() => UsersModule)],
  exports: [AuthService],
})
export class AuthModule {}
← Cours ▶ Mini-projet Module 04 →