Appearance
Les Injecteurs Hiérarchiques d'Angular
Imaginez que vous avez une boîte à outils dans votre garage (service global), mais aussi des outils spécifiques dans chaque pièce de votre maison (services locaux). Angular fonctionne de la même manière avec ses injecteurs hiérarchiques !
Pourquoi c'est important ?
Quand vous utilisez inject(UserService)
dans un composant, Angular doit savoir où trouver ce service. Il y a plusieurs endroits possibles, et Angular suit un ordre précis pour le chercher.
Analogie simple
C'est comme chercher un livre :
- D'abord dans votre chambre (composant)
- Puis dans le salon (composant parent)
- Enfin dans la bibliothèque commune (service global)
Les deux types d'injecteurs
Angular utilise deux types d'injecteurs qui travaillent ensemble :
Element Injectors (liés aux composants) Ces injecteurs sont créés avec vos composants et disparaissent quand le composant est supprimé.
Environment Injectors (liés à l'application) Ces injecteurs gèrent les services globaux et ceux des routes.
🎯 Element Injectors - Pour l'isolation et le cycle de vie
Problème résolu : Comment isoler des services temporaires sans polluer l'application ?
typescript
// Sans Element Injectors, ceci polluerait l'application globale
@Component({
selector: 'app-modal',
template: '<div>Modal content</div>'
})
export class ModalComponent {
// Ce service ne devrait exister QUE pendant que la modal est ouverte
private modalState = inject(ModalStateService);
}
Avantages :
- ✅ Nettoyage automatique : le service disparaît avec le composant
- ✅ Isolation : pas de conflit avec d'autres composants
- ✅ Performance : pas de services inutiles en mémoire
🌍 Environment Injectors - Pour le partage et la persistance
Problème résolu : Comment partager des services entre plusieurs composants ?
typescript
// Ces services doivent être partagés et persister
@Injectable({ providedIn: 'root' })
export class AuthService {
// Doit être accessible partout et rester en vie
}
@Injectable({ providedIn: 'root' })
export class ConfigService {
// Configuration globale de l'application
}
Avantages :
- ✅ Partage : même instance entre tous les composants
- ✅ Persistance : reste en vie pendant toute l'application
- ✅ Performance : une seule instance créée
🧠 L'analogie de la maison
Imaginez votre maison :
Element Injectors = Chaque pièce (chambre, cuisine, salon)
- Chaque pièce a ses propres outils spécifiques
- Quand vous quittez la pièce, les outils restent dedans
- Pas de pollution entre les pièces
Environment Injectors = Garage commun + Cave
- Outils partagés par toute la famille
- Restent disponibles en permanence
- Accessibles depuis n'importe quelle pièce
Comment Angular trouve un service
Quand vous écrivez inject(UserService)
, Angular suit cet ordre :
- Dans le composant : cherche d'abord dans l'injecteur du composant courant
- Remonte dans l'arbre : regarde dans les composants parents
- Dans la route : cherche dans l'injecteur de la route courante
- Globalement : enfin dans l'injecteur global de l'application
- Erreur : si rien n'est trouvé, affiche "No provider for UserService!"
Attention
Si Angular ne trouve pas le service, vous verrez l'erreur "No provider for X!" dans la console.
Element Injectors : services liés aux composants
Les Element Injectors sont créés avec vos composants et disparaissent quand le composant est supprimé.
Exemple simple
typescript
import { Component, inject } from '@angular/core';
import { UserCardService } from './user-card.service';
@Component({
selector: 'app-user-card',
template: '<div>{{ user.name }}</div>',
providers: [UserCardService] // Service uniquement pour ce composant
})
export class UserCardComponent {
private userCardService = inject(UserCardService);
user = input<User>();
}
Quand utiliser un Element Injector ?
- Service temporaire : logique spécifique à un composant
- Isolation : éviter qu'un service soit partagé entre composants
- Nettoyage automatique : le service disparaît avec le composant
Environment Injectors : services globaux et par route
Les Environment Injectors gèrent les services qui persistent dans l'application.
Service global
typescript
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root' // Disponible partout dans l'application
})
export class AuthService {
// Instance unique partagée
}
Service par route
typescript
import { Routes } from '@angular/router';
import { AdminService } from './admin.service';
import { UserListComponent } from './user-list.component';
export const routes: Routes = [
{
path: 'admin',
providers: [AdminService], // Disponible pour cette route
children: [
{ path: 'users', component: UserListComponent }
]
}
];
Quand utiliser un Environment Injector ?
- Service global : authentification, configuration
- Service par feature : logique métier d'un module
- Persistance : le service reste en vie pendant la navigation
Options avancées d'injection
Par défaut, inject()
suit l'ordre hiérarchique standard. Mais vous pouvez contrôler précisément où Angular cherche votre service avec des options spéciales.
Pourquoi contrôler la recherche ?
Parfois vous voulez :
- Un service local uniquement (pas celui du parent)
- Ignorer le service local et prendre celui du parent
- Un service optionnel (qui peut ne pas exister)
- Limiter la recherche à un niveau spécifique
🟦 { self: true }
- Cherche seulement ici
Signification : "Regarde uniquement dans mon composant, ne remonte pas au parent."
typescript
@Component({
selector: 'app-child',
template: '<div>Child</div>',
providers: [LocalService] // Obligatoire ici !
})
export class ChildComponent {
// Angular ne cherchera QUE dans ce composant
private localService = inject(LocalService, { self: true });
}
Attention
Si vous oubliez de fournir le service dans ce composant, Angular ne le cherchera pas chez le parent : il plantera !
Cas d'usage : Service temporaire, logique isolée, éviter les conflits avec le parent.
🟨 { skipSelf: true }
- Ignore-moi, cherche chez le parent
Signification : "Ne prends pas celui d'ici, va directement chez le parent."
typescript
@Component({
selector: 'app-parent',
providers: [ParentService],
template: '<app-child></app-child>'
})
export class ParentComponent {}
@Component({
selector: 'app-child',
providers: [ParentService], // Angular va ignorer celui-ci
template: '<div>Child</div>'
})
export class ChildComponent {
// Angular ignore le ParentService du Child et prend celui du Parent
private parentService = inject(ParentService, { skipSelf: true });
}
Cas d'usage : Éviter les conflits, forcer l'utilisation du service parent.
🟩 { optional: true }
- Si tu ne le trouves pas, pas grave
Signification : "Si le service n'existe pas, ne plante pas — donne-moi juste null
."
typescript
@Component({
selector: 'app-flexible',
template: '<div>Flexible component</div>'
})
export class FlexibleComponent {
// Ne plante pas si LoggerService n'existe pas
private logger = inject(LoggerService, { optional: true });
constructor() {
if (this.logger) {
this.logger.log('Logger trouvé !');
} else {
console.log('Pas de logger, et tout va bien');
}
}
}
Cas d'usage : Composants réutilisables, directives optionnelles, services de debug.
🏠 { host: true }
- Ne dépasse pas la limite de l'hôte
Signification : "Cherche le service seulement jusqu'à l'hôte (le composant sur lequel je suis appliqué)."
typescript
@Directive({
selector: '[tooltip]'
})
export class TooltipDirective {
// Cherche dans la directive ET le composant qui la porte, mais pas plus haut
private logger = inject(LoggerService, { host: true });
constructor() {
this.logger?.log('Tooltip active');
}
}
Cas d'usage : Directives, composants réutilisables, éviter la pollution globale.
📊 Résumé des options
Option | Image mentale 🧠 | Que fait Angular ? |
---|---|---|
{ self: true } | 🔒 "Cherche seulement ici" | Ne regarde que dans le composant courant |
{ skipSelf: true } | ⏭️ "Saute-moi" | Ignore l'injecteur courant, regarde chez le parent |
{ optional: true } | 🎩 "Pas grave s'il n'existe pas" | Retourne null au lieu d'une erreur |
{ host: true } | 🏠 "Ne sors pas de la maison" | Cherche jusqu'à l'hôte, pas plus haut |
🧠 Combinaisons possibles
Vous pouvez combiner plusieurs options :
typescript
// Cherche chez le parent, et si pas trouvé, ne plante pas
const logger = inject(LoggerService, { skipSelf: true, optional: true });
// Cherche seulement ici, mais ne plante pas si absent
const localLogger = inject(LoggerService, { self: true, optional: true });