À propos de ce cours
Objectifs de la leçon
- Injecter un ou plusieurs services par le constructeur.
- Connaître l'injection par propriété et ses limites.
- Déclarer une dépendance facultative avec @Optional.
- Obtenir un provider à la demande avec ModuleRef.
- Éviter les erreurs courantes d'injection.
Le constructeur avant tout ; les autres formes pour des cas particuliers.
1. L'injection par le constructeur
C'est la forme normale, utilisée dans 99 % des cas. Vous la connaissez depuis le chapitre 03 :
@Controller('stagiaires')
export class StagiairesController {
constructor(
private readonly stagiairesService: StagiairesService,
private readonly notesService: NotesService,
) {}
}
private: la propriété n'est utilisée qu'à l'intérieur du contrôleur.readonly: elle ne sera jamais réaffectée (le compilateur l'interdit).- L'ordre des paramètres n'a pas d'importance pour NestJS, qui se fie aux types.
- Le type doit être une classe (ou un jeton avec
@Inject, leçon 7.4) : une interface disparaît à la compilation.
Ne mettez pas de logique dans le constructeur (appel à un service, chargement de données) : il est exécuté pendant la construction du graphe d'objets, avant que l'application soit prête. Utilisez onModuleInit (leçon 5.6).
Un contrôleur qui utilise les autres formes
📄 src/services.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class GroupesService {
findAll() {
return ['DEV101', 'DEV102'];
}
}
@Injectable()
export class JournalService {
private lignes: string[] = [];
ecrire(message: string) {
this.lignes.push(message);
}
get nombre() {
return this.lignes.length;
}
}
@Injectable()
export class StatistiquesService {
calculer() {
return { groupes: 2 };
}
}
📄 src/groupes.controller.ts
import { Controller, Get, Inject, Optional } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { GroupesService, JournalService, StatistiquesService } from './services';
class CacheService {} // n'est déclaré dans aucun module
@Controller('groupes')
export class GroupesController {
// 2. Injection par propriété (rare : réservée aux cas d'héritage)
@Inject(JournalService)
private readonly journal: JournalService;
// 1. Injection par le constructeur (la forme normale)
constructor(
private readonly groupesService: GroupesService,
@Optional() private readonly cache?: CacheService, // 3. dépendance facultative
private readonly moduleRef?: ModuleRef, // 4. accès programmatique au conteneur
) {}
@Get()
findAll() {
this.journal.ecrire('liste des groupes');
return {
groupes: this.groupesService.findAll(),
cacheDisponible: this.cache !== undefined,
lignesJournal: this.journal.nombre,
};
}
@Get('statistiques')
statistiques() {
const stats = this.moduleRef!.get(StatistiquesService); // obtenu à la demande
return stats.calculer();
}
}
Réponses obtenues :
GET /api/groupes → 200 {"groupes":["DEV101","DEV102"],"cacheDisponible":false,"lignesJournal":1}
GET /api/groupes → 200 {"groupes":["DEV101","DEV102"],"cacheDisponible":false,"lignesJournal":2}
GET /api/groupes/statistiques → 200 {"groupes":2}
2. L'injection par propriété
@Inject(JournalService) sur une propriété demande à NestJS de la remplir après la création de l'objet. Le compteur lignesJournal qui passe de 1 à 2 montre que le journal (un singleton) a bien été injecté. Cette forme est déconseillée en général : la dépendance n'apparaît pas dans le constructeur, et la propriété n'est pas disponible pendant celui-ci. Elle est utile dans un cas précis : une classe de base héritée par plusieurs classes, pour éviter que chaque classe fille répète le super(…) avec toutes les dépendances.
3. Les dépendances facultatives
CacheService n'est déclaré dans aucun module. Sans @Optional(), l'application refuserait de démarrer (« Nest can't resolve dependencies… »). Avec, NestJS injecte undefined: la réponse indique cacheDisponible: false. Le code doit alors tester la présence (this.cache?.lire(…)).
4. ModuleRef : obtenir un provider à la demande
ModuleRef (de @nestjs/core) est le conteneur lui-même, injectable comme n'importe quel provider. moduleRef.get(Classe) renvoie l'instance d'un provider du module. C'est utile quand le provider à utiliser n'est connu qu'à l'exécution (choisir un service selon un paramètre), mais à éviter quand l'injection classique suffit : la dépendance devient invisible.
// Exemple d'usage légitime : choisir une stratégie à l'exécution
@Injectable()
export class ExportService {
constructor(private readonly moduleRef: ModuleRef) {}
exporter(format: 'pdf' | 'csv', donnees: unknown[]) {
const generateur = format === 'pdf' ? this.moduleRef.get(ExportPdf) : this.moduleRef.get(ExportCsv);
return generateur.generer(donnees);
}
}
Erreurs courantes
| Symptôme | Cause |
|---|---|
| « Nest can't resolve dependencies of the XController (?) » | service non déclaré dans providers, ou non exporté/importé (leçon 5.2) |
| Le message parle d'un argument « undefined at runtime » | import type utilisé pour une classe injectée, ou import circulaire entre fichiers (leçon 7.6) |
| Erreur « Cannot read properties of undefined » dans une méthode | @Injectable() oublié sur la classe qui reçoit (leçon 7.2) |
| Type de paramètre écrit comme une interface | l'interface n'existe pas à l'exécution : utiliser une classe (abstraite) ou un jeton + @Inject |
this.service undefined alors que tout est déclaré | méthode passée comme fonction de rappel (liste.map(this.service.transformer)) : this est perdu ; écrire (x) => this.service.transformer(x) |
Combien de dépendances ? Si un constructeur reçoit plus de 4 ou 5 services, c'est souvent le signe que la classe fait trop de choses. Découpez-la, ou regroupez une partie de la logique dans un service intermédiaire.
Activités
Activité 1 Injecter deux services
Créez un TableauDeBordController (GET /api/tableau-de-bord) qui injecte StagiairesService et GroupesService et renvoie { stagiaires: n, groupes: m }. Où déclarer le contrôleur pour que les deux services soient disponibles ?
Voir le corrigé
Dans un module qui a accès aux deux services : soit le même module que ces services, soit un nouveau TableauDeBordModule qui importe StagiairesModule et GroupesModule, à condition qu'ils exportent leurs services (leçon 5.2).
Activité 2 Dépendance facultative
Ajoutez à NotesService une dépendance facultative NotificationsService qui envoie un message au stagiaire après chaque note. L'application doit fonctionner avec ou sans ce service déclaré. Testez les deux cas.
Voir le corrigé
@Injectable()
export class NotesService {
constructor(
private readonly stagiairesService: StagiairesService,
@Optional() private readonly notifications?: NotificationsService,
) {}
ajouter(stagiaireId: number, matiere: string, valeur: number) {
// … enregistrement …
this.notifications?.envoyer(stagiaireId, `Nouvelle note en ${matiere} : ${valeur}`);
return note;
}
}
Sans NotificationsService dans les providers, la note est enregistrée sans message ; avec, le message est envoyé. Le ? du paramètre et le ?. de l'appel sont indispensables.
Activité 3 Corriger l'erreur
Ce contrôleur démarre mais renvoie 500 sur GET /api/stagiaires/noms. Trouvez la cause.
@Controller('stagiaires')
export class StagiairesController {
constructor(private readonly stagiairesService: StagiairesService) {}
@Get('noms')
noms() {
return this.stagiairesService.findAll().map(this.formater);
}
formater(s: Stagiaire) {
return this.stagiairesService.nomComplet(s);
}
}
Voir le corrigé
.map(this.formater) passe la méthode sans son objet : à l'intérieur, this vaut undefined, donc this.stagiairesService provoque l'erreur. Correction : .map((s) => this.formater(s)). Une fonction fléchée conserve le this du contexte (leçon 4.1).
Activité 4 Choisir à l'exécution avec ModuleRef
Écrivez deux services ExportCsv et ExportJson ayant une méthode generer(liste), et une route GET /api/stagiaires/export?format=csv|json qui utilise ModuleRef pour choisir le service. Proposez ensuite une version sans ModuleRef, en injectant les deux services. Laquelle préférez-vous ?
Voir le corrigé
Avec deux implémentations seulement, injecter les deux et choisir avec un if est plus lisible et plus facile à tester : les dépendances sont visibles dans le constructeur. ModuleRef devient intéressant quand les implémentations sont nombreuses ou chargées dynamiquement.
À retenir
- Injection par le constructeur avec private readonly : la forme à utiliser par défaut.
- NestJS se fie aux types des paramètres, pas à leur ordre ; le type doit exister à l'exécution.
- @Inject sur une propriété : rare, utile pour l'héritage.
- @Optional() : dépendance facultative, injectée à undefined si absente.
- ModuleRef.get() : obtenir un provider à la demande, pour des cas avancés.