Appearance
InjectionToken avec factory en Angular
Les InjectionTokens avec factory permettent de créer des tokens Angular qui peuvent générer leurs propres valeurs par défaut tout en restant surchargeables. C'est comme avoir un fournisseur automatique qui sait quoi faire quand personne d'autre n'a défini comment fournir une valeur.
Le principe fondamental
Un InjectionToken avec factory est un token qui contient une fonction que Angular exécutera automatiquement si aucun provider n'a été défini pour ce token. C'est une façon élégante de fournir des valeurs par défaut tout en gardant la flexibilité.
typescript
import { InjectionToken, inject } from '@angular/core';
// InjectionToken simple avec factory
export const ENV_ID = new InjectionToken<string>(
'Identifiant de l'environnement',
{
providedIn: 'root',
factory: () => {
// Cette fonction s'exécute si aucun provider n'a été défini
return Math.random().toString(36).substring(2);
}
}
);
Dans cet exemple, chaque fois que inject(ENV_ID)
est appelé, Angular :
- Cherche un provider pour
ENV_ID
- N'en trouve aucun → exécute la factory
- Met le résultat en cache dans le root injector
- Réutilise cette valeur pour toutes les autres injections
Exemple concret : API URL avec fallback
Créons un exemple pratique pour gérer l'URL de base de notre API avec une valeur par défaut :
typescript
import { InjectionToken } from '@angular/core';
// Token pour l'URL de base de l'API
export const API_URL = new InjectionToken<string>(
'URL de base pour l\'API',
{
providedIn: 'root',
factory: () => 'https://api.monapp.com/v1'
}
);
Maintenant, créons un service qui utilise ce token :
typescript
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { API_URL } from './api-url.token';
@Injectable({
providedIn: 'root'
})
export class UserService {
// Injection du token avec factory
private apiUrl = inject(API_URL);
private http = inject(HttpClient);
// Méthode pour récupérer les utilisateurs
getUsers() {
return this.http.get<User[]>(`${this.apiUrl}/users`);
}
}
Avantage
Le service récupère automatiquement l'URL par défaut sans configuration supplémentaire, mais cette URL peut être surchargée si nécessaire.
Surcharger la factory avec un provider
La beauté des InjectionTokens avec factory est qu'ils peuvent être remplacés par des providers explicites :
typescript
import { Component } from '@angular/core';
import { API_URL } from './api-url.token';
@Component({
selector: 'app-production',
template: '<h1>Mode Production</h1>',
providers: [
// Surcharge de l'URL par défaut
{ provide: API_URL, useValue: 'https://api-prod.monapp.com/v1' }
]
})
export class ProductionComponent {
// Ce composant utilisera l'URL de production
}
Ordre de priorité
Angular utilise toujours le provider explicite s'il existe, la factory n'est qu'un fallback.
Factory avec dépendances injectables
Depuis Angular 14+, les factory peuvent injecter d'autres dépendances. C'est très puissant pour créer des configurations dynamiques :
typescript
import { InjectionToken, inject } from '@angular/core';
// Service pour détecter l'environnement
@Injectable({
providedIn: 'root'
})
export class EnvironmentService {
get mode(): string {
return window.location.hostname === 'localhost' ? 'development' : 'production';
}
}
// Token qui dépend d'un autre service
export const APP_MODE = new InjectionToken<string>(
'Mode de l\'application',
{
providedIn: 'root',
factory: () => {
// La factory peut injecter d'autres services
const envService = inject(EnvironmentService);
return envService.mode;
}
}
);
Cette approche permet de créer des tokens intelligents qui s'adaptent automatiquement à leur environnement.
Configuration avancée avec plusieurs dépendances
Créons un exemple plus complexe qui combine plusieurs tokens :
typescript
import { InjectionToken, inject } from '@angular/core';
// Interface pour la configuration de l'API
export interface ApiConfig {
baseUrl: string;
timeout: number;
retries: number;
}
// Token pour la configuration complète de l'API
export const API_CONFIG = new InjectionToken<ApiConfig>(
'Configuration complète de l\'API',
{
providedIn: 'root',
factory: () => {
// Injection de plusieurs dépendances dans la factory
const envService = inject(EnvironmentService);
return {
baseUrl: envService.mode === 'production'
? 'https://api.monapp.com/v1'
: 'http://localhost:3000/api',
timeout: 5000,
retries: 3
};
}
}
);
Utilisation dans un composant
Créons un composant qui utilise notre token avec factory :
typescript
import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
import { API_CONFIG } from './api-config.token';
@Component({
selector: 'app-user-list',
template: `
<div class="user-list">
<h2>Liste des utilisateurs</h2>
@if (users.length > 0) {
@for (user of users; track user.id) {
<div class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
}
} @else {
<p>Aucun utilisateur trouvé</p>
}
</div>
`
})
export class UserListComponent {
// Injection des services et tokens
private userService = inject(UserService);
private apiConfig = inject(API_CONFIG);
users: User[] = [];
ngOnInit() {
// Utilisation du service qui utilise le token avec factory
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
}
Bonne pratique
Utilisez les InjectionTokens avec factory pour centraliser la logique de configuration et éviter la duplication de code.
Cas d'usage avancés
Token pour des valeurs calculées
typescript
export const CACHE_DURATION = new InjectionToken<number>(
'Durée du cache en millisecondes',
{
providedIn: 'root',
factory: () => {
// Calcul dynamique basé sur l'heure
const hour = new Date().getHours();
return hour < 9 || hour > 18 ? 3600000 : 1800000; // 1h ou 30min
}
}
);
Token avec validation
typescript
export const FEATURE_FLAGS = new InjectionToken<Record<string, boolean>>(
'Feature flags de l\'application',
{
providedIn: 'root',
factory: () => {
const flags = localStorage.getItem('feature-flags');
try {
return flags ? JSON.parse(flags) : {};
} catch {
console.warn('Feature flags invalides, utilisation des valeurs par défaut');
return {};
}
}
}
);
À retenir
Un InjectionToken avec factory est un provider autonome : il peut produire sa propre valeur sans configuration externe, tout en restant surchargeable si un provider explicite est ajouté.