Injection de Dépendances Angular Avancée en 2026 : Providers, Tokens et Questions d'Entretien
Maîtriser l'injection de dépendances Angular avec InjectionToken, les injecteurs hiérarchiques, les modificateurs de résolution et les multi-providers. Inclut des questions d'entretien et des patterns de production.

Le système d'injection de dépendances d'Angular offre un contrôle précis sur l'instanciation des services grâce aux providers, tokens et injecteurs hiérarchiques. La maîtrise de ces concepts distingue les développeurs Angular expérimentés des juniors lors des entretiens techniques et dans les bases de code en production.
Angular maintient deux arbres d'injecteurs parallèles : l'arbre ModuleInjector pour les services fournis au niveau module, et l'arbre ElementInjector pour les dépendances scopées aux composants. La résolution commence au niveau de l'élément et remonte jusqu'à la racine.
Stratégies de Configuration des Providers dans Angular DI
Angular propose plusieurs stratégies de configuration des providers, chacune adaptée à des cas d'usage différents. Les configurations les plus courantes utilisent useClass, useValue, useFactory et useExisting.
import { ApplicationConfig, InjectionToken } from '@angular/core';
import { LoggerService } from './services/logger.service';
import { DebugLoggerService } from './services/debug-logger.service';
import { API_CONFIG, ApiConfig } from './config/api.config';
export const appConfig: ApplicationConfig = {
providers: [
// useClass: fournir une implémentation différente
{ provide: LoggerService, useClass: DebugLoggerService },
// useValue: fournir un objet de configuration statique
{
provide: API_CONFIG,
useValue: { baseUrl: 'https://api.example.com', timeout: 5000 }
},
// useFactory: créer une dépendance avec logique runtime
{
provide: 'FEATURE_FLAGS',
useFactory: () => {
const env = import.meta.env.MODE;
return { debugMode: env === 'development', analytics: env === 'production' };
}
},
// useExisting: créer un alias vers un autre provider
{ provide: 'Logger', useExisting: LoggerService }
]
};La stratégie useClass permet d'échanger les implémentations sans modifier le code consommateur. La stratégie useValue fournit des objets statiques comme la configuration. La stratégie useFactory gère les décisions runtime, et useExisting crée des alias pour un accès polymorphique.
InjectionToken pour les Dépendances Non-Classe Type-Safe
Alors que @Injectable fonctionne pour les services basés sur des classes, les valeurs non-classe comme les objets de configuration, les primitives ou les fonctions nécessitent InjectionToken. Ce token agit comme une clé unique dans le registre DI d'Angular.
import { InjectionToken } from '@angular/core';
export interface ApiConfig {
baseUrl: string;
timeout: number;
retryAttempts: number;
}
// Le paramètre générique assure la sécurité de type au point d'injection
export const API_CONFIG = new InjectionToken<ApiConfig>('api.config', {
providedIn: 'root',
factory: () => ({
baseUrl: 'https://api.sharpskill.dev',
timeout: 30000,
retryAttempts: 3
})
});
// Token pour les valeurs primitives
export const MAX_UPLOAD_SIZE = new InjectionToken<number>('max.upload.size', {
providedIn: 'root',
factory: () => 10 * 1024 * 1024 // 10MB
});Le paramètre de type générique sur InjectionToken<ApiConfig> se propage à l'appel inject(). TypeScript sait que la valeur injectée correspond au type déclaré, détectant les erreurs d'utilisation à la compilation plutôt qu'à l'exécution.
La Fonction inject() vs l'Injection par Constructeur
Angular 14 a introduit la fonction inject() comme alternative à l'injection basée sur le constructeur. Dans Angular 20+, inject() est devenue l'approche privilégiée, particulièrement dans les composants standalone et les contextes fonctionnels.
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { API_CONFIG } from '../tokens/config.tokens';
@Injectable({ providedIn: 'root' })
export class UserService {
// Approche moderne: inject() au niveau du champ
private readonly http = inject(HttpClient);
private readonly config = inject(API_CONFIG);
getUser(id: string) {
return this.http.get(`${this.config.baseUrl}/users/${id}`);
}
}
// Alternative: injection par constructeur (toujours valide)
@Injectable({ providedIn: 'root' })
export class UserServiceLegacy {
constructor(
private readonly http: HttpClient,
@Inject(API_CONFIG) private readonly config: ApiConfig
) {}
}La fonction inject() élimine le boilerplate des décorateurs pour les tokens et permet l'injection de dépendances dans des contextes non-classe comme les guards, resolvers et interceptors fonctionnels.
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
// Guard fonctionnel utilisant inject()
export const authGuard: CanActivateFn = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true;
}
return router.createUrlTree(['/login']);
};La fonction inject() ne fonctionne que dans un contexte d'injection : pendant la construction de classe, dans les fonctions factory, ou dans les constructs Angular fonctionnels. L'appeler en dehors de ces contextes provoque une erreur runtime.
Prêt à réussir tes entretiens Angular ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Injecteurs Hiérarchiques : ElementInjector vs EnvironmentInjector
Angular maintient deux hiérarchies d'injecteurs parallèles qui déterminent la portée et l'ordre de résolution des services. Comprendre cette architecture est essentiel pour contrôler la durée de vie et la visibilité des services.
import { Injectable } from '@angular/core';
// Singleton au niveau racine: une seule instance dans toute l'app
@Injectable({ providedIn: 'root' })
export class GlobalDataService {
private data = new Map<string, unknown>();
set(key: string, value: unknown) { this.data.set(key, value); }
get(key: string) { return this.data.get(key); }
}
// Scopé au composant: nouvelle instance par composant
@Injectable()
export class ComponentDataService {
private data = new Map<string, unknown>();
set(key: string, value: unknown) { this.data.set(key, value); }
get(key: string) { return this.data.get(key); }
}import { Component } from '@angular/core';
import { ComponentDataService } from './services/component-data.service';
@Component({
selector: 'app-dashboard',
standalone: true,
// Ce composant et tous ses enfants obtiennent la même instance
providers: [ComponentDataService],
template: `
<app-widget />
<app-stats />
`
})
export class DashboardComponent {}Quand un composant déclare un provider, Angular crée une nouvelle instance scopée à l'ElementInjector de ce composant. Les composants enfants héritent de l'accès aux providers du parent sauf s'ils déclarent les leurs.
L'algorithme de résolution suit ce chemin :
- Vérifier l'ElementInjector du composant demandeur
- Remonter l'arbre ElementInjector jusqu'aux ancêtres
- Vérifier l'EnvironmentInjector (module ou providers standalone)
- Remonter jusqu'à l'EnvironmentInjector racine
- Atteindre le NullInjector et lever une erreur si
@Optional()n'a pas été utilisé
Modificateurs de Résolution : @Self, @SkipSelf, @Optional, @Host
Les modificateurs de résolution modifient la façon dont Angular parcourt la hiérarchie des injecteurs. Ces décorateurs fonctionnent avec l'injection par constructeur et la fonction inject().
import { Component, Optional, SkipSelf, Self, inject } from '@angular/core';
import { PanelService } from './panel.service';
@Component({
selector: 'app-panel',
standalone: true,
providers: [PanelService],
template: `<ng-content />`
})
export class PanelComponent {
// @Self: chercher uniquement dans l'injecteur de ce composant
private readonly localService = inject(PanelService, { self: true });
// @SkipSelf: ignorer ce composant, commencer la recherche depuis le parent
private readonly parentService = inject(PanelService, {
skipSelf: true,
optional: true
});
// @Optional: retourner null au lieu de lever une erreur si non trouvé
private readonly optionalService = inject(PanelService, { optional: true });
constructor() {
// localService est toujours l'instance propre du composant
// parentService est l'instance du parent ou null
console.log('Local:', this.localService);
console.log('Parent:', this.parentService);
}
}Le modificateur @Host() restreint la résolution à l'injecteur de l'élément hôte et s'arrête aux frontières des composants. C'est utile quand une directive doit accéder à un service fourni par son composant hôte mais ne doit pas remonter plus haut dans l'arbre.
import { Directive, inject, Host, Optional } from '@angular/core';
import { HighlightConfig } from './highlight.config';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
// Ne chercher que dans les providers du composant hôte
private readonly config = inject(HighlightConfig, {
host: true,
optional: true
});
constructor() {
// config est null si le composant hôte n'a pas fourni HighlightConfig
const color = this.config?.color ?? 'yellow';
// Appliquer le surlignage...
}
}Multi-Providers pour des Systèmes Extensibles
Les multi-providers permettent d'enregistrer plusieurs valeurs sous un seul token. Angular retourne toutes les valeurs enregistrées sous forme de tableau, permettant des architectures de plugins et des patterns d'extensibilité.
import { InjectionToken } from '@angular/core';
export interface Validator {
validate(value: string): string | null;
}
export const VALIDATORS = new InjectionToken<Validator[]>('validators');import { ApplicationConfig } from '@angular/core';
import { VALIDATORS } from './validators.tokens';
const requiredValidator = {
validate: (value: string) => value ? null : 'Le champ est requis'
};
const minLengthValidator = {
validate: (value: string) => value.length >= 3 ? null : 'Minimum 3 caractères'
};
const emailValidator = {
validate: (value: string) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : 'Format email invalide'
};
export const appConfig: ApplicationConfig = {
providers: [
{ provide: VALIDATORS, useValue: requiredValidator, multi: true },
{ provide: VALIDATORS, useValue: minLengthValidator, multi: true },
{ provide: VALIDATORS, useValue: emailValidator, multi: true }
]
};import { Injectable, inject } from '@angular/core';
import { VALIDATORS, Validator } from './validators.tokens';
@Injectable({ providedIn: 'root' })
export class ValidationService {
private readonly validators = inject(VALIDATORS);
validate(value: string): string[] {
// validators est un tableau de tous les validateurs enregistrés
return this.validators
.map(v => v.validate(value))
.filter((error): error is string => error !== null);
}
}Le flag multi: true indique à Angular de collecter tous les providers pour ce token dans un tableau. Sans lui, les providers ultérieurs écraseraient les précédents.
Questions d'Entretien sur l'Injection de Dépendances Angular
Les entretiens techniques sondent fréquemment la compréhension du système DI d'Angular. Voici des questions qui distinguent les candidats avec une expérience en production.
Q : Que se passe-t-il quand on fournit le même service au niveau module et composant ?
Le provider au niveau composant crée une instance séparée scopée au sous-arbre de ce composant. Les services injectés dans ce sous-arbre reçoivent l'instance du composant, pas le singleton au niveau module. Cela permet l'isolation d'état, par exemple quand chaque onglet a besoin de son propre état de formulaire.
Q : Pourquoi utiliser InjectionToken au lieu d'un littéral string ?
Les tokens string risquent des collisions entre bibliothèques ou différentes parties d'une application. InjectionToken crée une référence runtime unique qui ne peut pas entrer en conflit. Le paramètre de type générique fournit aussi une sécurité de type à la compilation que les tokens string n'ont pas.
Q : Quand inject() lève-t-il une erreur vs retourne undefined ?
Par défaut, inject() lève une erreur quand la dépendance n'est pas trouvée. Passer { optional: true } change le type de retour en T | null et retourne null au lieu de lever une erreur. Cela correspond au comportement du décorateur @Optional().
Q : Expliquer la différence entre providedIn: 'root' et fournir dans le tableau providers d'un module.
Les deux créent des singletons, mais providedIn: 'root' permet le tree-shaking. Le service n'est inclus dans le bundle que s'il est réellement injecté quelque part. Les providers au niveau module sont toujours inclus indépendamment de leur utilisation.
Q : Comment le lazy loading affecte-t-il la portée des services ?
Les modules lazy-loadés obtiennent leur propre EnvironmentInjector enfant. Les services fournis dans un module lazy sont scopés à ce module et ses enfants. Un service avec providedIn: 'root' reste un vrai singleton à travers tous les modules, lazy ou non.
Pour des questions pratiques sur les services Angular et les patterns DI, consultez les questions d'entretien Angular sur les services et l'injection de dépendances.
Patterns Pratiques : Configuration et Feature Flags
Les applications réelles combinent ces concepts DI pour la gestion de configuration. Ce pattern utilise InjectionToken, les factory providers et la connaissance de l'environnement.
import { InjectionToken, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
export interface FeatureFlags {
newCheckout: boolean;
darkMode: boolean;
betaFeatures: boolean;
}
export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>('feature.flags', {
providedIn: 'root',
factory: () => {
const platformId = inject(PLATFORM_ID);
if (!isPlatformBrowser(platformId)) {
// SSR: retourner des valeurs par défaut sûres
return { newCheckout: false, darkMode: false, betaFeatures: false };
}
// Navigateur: vérifier localStorage ou config distante
const stored = localStorage.getItem('featureFlags');
if (stored) {
return JSON.parse(stored);
}
return { newCheckout: true, darkMode: false, betaFeatures: false };
}
});import { Directive, Input, TemplateRef, ViewContainerRef, inject } from '@angular/core';
import { FEATURE_FLAGS } from './feature-flags.config';
@Directive({
selector: '[appFeatureFlag]',
standalone: true
})
export class FeatureFlagDirective {
private readonly flags = inject(FEATURE_FLAGS);
private readonly templateRef = inject(TemplateRef<unknown>);
private readonly viewContainer = inject(ViewContainerRef);
@Input() set appFeatureFlag(flag: keyof typeof this.flags) {
if (this.flags[flag]) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}Cette directive affiche conditionnellement du contenu basé sur les feature flags, avec la configuration des flags centralisée dans un seul token injectable.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Pratiques DI Angular Prêtes pour la Production
- Utiliser
providedIn: 'root'pour les singletons à l'échelle de l'application qui bénéficient du tree-shaking - Préférer
inject()à l'injection par constructeur dans Angular 20+ pour une syntaxe plus propre et la compatibilité fonctionnelle - Scoper les services avec état aux composants quand l'isolation est nécessaire, pas au niveau module
- Créer des
InjectionTokenpour les dépendances non-classe pour assurer la sécurité de type et éviter les collisions - Appliquer
@Optional()quand une dépendance pourrait ne pas exister, particulièrement pour les plugins ou fonctionnalités optionnelles - Tester les composants avec des providers surchargés en utilisant
TestBed.overrideComponent()pour l'isolation - Utiliser les multi-providers pour les patterns d'extensibilité comme les validateurs, interceptors et handlers
Tu saurais repérer le bug en Angular ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 13 septembre 2026
Tags
Partager
Articles similaires

Angular Signals et Computed en 2026 : Réactivité Fine et Questions d'Entretien
Maîtriser les Signals Angular, computed, effect et linkedSignal dans Angular 20+. Patterns de réactivité fine et préparation aux entretiens techniques.

Angular HttpClient et Interceptors en 2026 : Gestion des Requêtes et Questions d'Entretien
Guide complet sur HttpClient et les interceptors Angular en 2026. Architecture des requêtes HTTP, gestion des erreurs, authentification et questions d'entretien technique.

RxJS dans Angular 2026 : operators, Subjects et interop Signals
RxJS dans Angular 2026 : maîtriser les operators, les Subjects et les patterns d'interop Signals utilisés en production, ainsi que les questions d'entretien les plus fréquentes.