🔴 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.

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 :

  1. D'abord dans votre chambre (composant)
  2. Puis dans le salon (composant parent)
  3. 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 :

  1. Dans le composant : cherche d'abord dans l'injecteur du composant courant
  2. Remonte dans l'arbre : regarde dans les composants parents
  3. Dans la route : cherche dans l'injecteur de la route courante
  4. Globalement : enfin dans l'injecteur global de l'application
  5. 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

OptionImage 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 });