🔴 Live Angular le 16 octobre à 19h

Anticipez le futur avec Signal Forms d'Angular

Angular 21 introduira Signal Forms, une nouvelle API expérimentale qui simplifiera radicalement la gestion des formulaires.

Basée sur les signaux, elle permettra :

  • de créer des formulaires déclaratifs avec form()
  • de lier directement les champs avec [control]
  • d'intégrer facilement les validations et la soumission
  • et de réduire le boilerplate tout en améliorant les performances
Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

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 :

  1. Cherche un provider pour ENV_ID
  2. N'en trouve aucun → exécute la factory
  3. Met le résultat en cache dans le root injector
  4. 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é.