Appearance
Comment utiliser useFactory dans Angular pour créer des services configurables ?
useFactory est une fonctionnalité puissante d'Angular qui permet de créer des instances de services de manière dynamique et configurable. C'est particulièrement utile quand la création d'un service nécessite une logique complexe ou des paramètres qui ne sont connus qu'à l'exécution.
Comprendre useFactory avec un exemple concret
Imaginons que vous développez une application de messagerie instantanée :
En production :
- Connexion au serveur de production
- Messages en temps réel
- Notifications push
En développement :
- Connexion au serveur local
- Délai simulé pour les messages
- Notifications console
useFactory nous permet de configurer notre service de messagerie différemment selon l'environnement, tout en gardant la même interface d'utilisation.
Différences avec les autres providers
Provider | Description | Exemple | Cas d'utilisation | Documentation |
---|---|---|---|---|
useClass | Remplace complètement un service par une autre classe | { provide: UserService, useClass: MockUserService } | Quand vous voulez une implémentation complètement différente (ex: mock pour les tests) | En savoir plus |
useValue | Fournit une valeur fixe | { provide: API_URL, useValue: 'https://api.example.com' } | Pour des constantes ou des objets simples prédéfinis | En savoir plus |
useFactory | Crée dynamiquement une valeur via une fonction | { provide: UserService, useFactory: () => environment.production ? new RealUserService() : new MockUserService() } | Quand la valeur dépend de conditions ou nécessite une logique de création | En savoir plus |
useExisting | Crée un alias vers un service existant | { provide: AbstractLogger, useExisting: ConsoleLogger } | Quand vous voulez utiliser un service existant sous un autre nom | En savoir plus |
Mise en pratique avec notre gestionnaire d'utilisateurs
Voici comment implémenter un service d'utilisateurs configurable :
ts
import { HttpClient } from '@angular/common/http';
import { UserService } from './user.service';
export function userServiceFactory(http: HttpClient, environment: string) {
const config = {
production: {
apiUrl: 'https://api.example.com/users',
cacheTimeout: 3600000 // 1 heure
},
development: {
apiUrl: 'http://localhost:3000/users',
cacheTimeout: 0 // pas de cache en dev
}
};
const env = environment === 'production' ? 'production' : 'development';
return new UserService(
http,
config[env].apiUrl,
config[env].cacheTimeout
);
}
ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { User } from './user';
import { Observable } from 'rxjs';
@Injectable()
export class UserService {
constructor(
private http: HttpClient,
private apiUrl: string,
private cacheTimeout: number
) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
}
ts
export interface User {
id: number;
name: string;
username?: string;
email: string;
address?: {
street: string;
suite: string;
city: string;
zipcode: string;
geo: {
lat: string;
lng: string;
}
};
phone?: string;
website?: string;
company?: {
name: string;
catchPhrase: string;
bs: string;
};
}
CONSEIL
Une factory function permet d'encapsuler toute la logique de création et de configuration d'un service dans un seul endroit, rendant le code plus maintenable.
Configuration du provider dans votre application :
ts
import { ApplicationConfig } from '@angular/core';
import { UserService } from './user.service';
import { userServiceFactory } from './user.factory';
import { ENVIRONMENT } from './environment.token';
export const appConfig: ApplicationConfig = {
providers: [
{
provide: ENVIRONMENT,
useValue: 'production' // ou process.env.NODE_ENV
},
{
provide: UserService,
useFactory: userServiceFactory,
deps: [HttpClient, ENVIRONMENT]
}
]
};
ts
import { InjectionToken } from '@angular/core';
export const ENVIRONMENT = new InjectionToken<string>('environment');
Pour comprendre simplement :
typescript
{
provide: UserService, // "Je veux un service utilisateur"
useFactory: userServiceFactory, // "Utilise cette fonction pour le créer"
deps: [HttpClient, ENVIRONMENT] // "Voici ce dont tu as besoin pour le créer"
}
IMPORTANT
La propriété deps
doit contenir tous les services et valeurs nécessaires à votre factory function, dans le même ordre que les paramètres de la fonction.
Rappel
Un InjectionToken
est un token unique qui permet d'injecter des valeurs qui ne sont pas des classes. C'est particulièrement utile pour :
- Injecter des valeurs primitives (string, number, etc.)
- Éviter les collisions de noms
- Typer correctement les dépendances injectées
En savoir plus : InjectionToken
IMPORTANT
Dans un vrai projet, la valeur de l'environnement devrait venir de vos fichiers d'environnement Angular (environment.ts
et environment.prod.ts
) ou de variables d'environnement.
En savoir plus : Environnement
Cas d'utilisation typiques
Configuration dynamique :
- URLs différentes selon l'environnement
- Timeouts configurables
- Stratégies de cache adaptatives
Logique conditionnelle :
- Différentes implémentations selon les features flags
- Adaptation selon la plateforme (web/mobile)
- Comportements spécifiques selon l'utilisateur
Tests :
- Configuration spécifique pour les tests
- Injection de mocks avec paramètres
- Simulation de scénarios complexes
BONNE PRATIQUE
Pour les tests, vous pouvez créer une factory spécifique :
ts
export function testUserServiceFactory(http: HttpClient) {
return new UserService(
http,
'http://test-api/users',
0
);
}
TestBed.configureTestingModule({
providers: [
{
provide: UserService,
useFactory: testUserServiceFactory,
deps: [HttpClient]
}
]
});