Angular EnterpriseAngular Enterprise

Injection de dépendances en Angular

  • Cours
  • 20 pratiques
  • 6 min de lecture
Injection de dépendances en Angular

L'injection de dépendances (DI) est un modèle de conception dans lequel une classe demande des dépendances à des sources externes (les injecteurs) plutôt que de les créer elle-même. Angular a son propre framework DI qui aide à écrire des applications modulaires. Les dépendances ne sont rien d'autre que des services ou des objets avec un cycle de vie clair en fonction de leur configuration.

Les injecteurs sont hérités, ce qui signifie que si un injecteur donné ne peut pas résoudre une dépendance, il demande à l'injecteur parent. Un composant ou une directive peut obtenir des services de son propre injecteur, des injecteurs de ses composants ancêtres, de l'injecteur de sa route ou de son NgModule parent, ou de l'injecteur racine (créé par bootstrapApplication ou le module d'application).

Les dépendances peuvent être demandées via les paramètres du constructeur ou avec la fonction inject(), disponible depuis Angular 14 et désormais l'approche recommandée dans le code Angular moderne.

Organiser les dépendances

  • Tree-shakable services sont possibles depuis Angular version 6 en ajoutant providedIn: 'root' (ou 'platform') directement dans le décorateur @Injectable() du service : si le service n'est jamais injecté, il sera supprimé du bundle lors de la compilation.
  • Core services (non lazy) doivent être dans le dossier core et peuvent être déclarés soit dans le tableau providers: [] de bootstrapApplication (ou du module core), soit en utilisant la syntaxe providedIn: 'root' dans leur décorateur @Injectable(), puis ils peuvent être utilisés partout (lazy ou non) sans les mettre dans le tableau providers: [] d'un autre module.
  • Shared services (partagés par plusieurs modules lazy ou initiaux) : si vous placez vos services dans le tableau providers du module partagé et que vous importez ensuite ce module, comme prévu, dans plusieurs modules lazy, alors chaque module chargé en lazy obtiendra sa propre instance du service et non le singleton attendu. Cela est dû au fait que les modules chargés en lazy ont leurs propres injecteurs. La solution la plus simple est providedIn: 'root'. Sinon vous pouvez utiliser l'interface ModuleWithProviders et créer deux méthodes statiques, forRoot()/forChild(), afin que les providers ne soient enregistrés qu'une seule fois alors que le module est importé dans des modules initiaux et lazy. Cette solution est utilisée par le framework Angular lui-même pour le service Router du RouterModule.
  • Feature services (route ou module lazy) peuvent être scopés à cette feature en supprimant le providedIn: 'root' du décorateur @Injectable() et en les ajoutant au tableau providers: [] de la route lazy ou du module lazy à la place. De cette manière le lazy loading du service est explicitement fait avec la feature lazy, mais sachez qu'avec providedIn: 'root' le compilateur Angular va également faire ceci si et uniquement si votre service est utilisé uniquement dans cette feature lazy.
  • Component services peuvent être scopés à ce composant en supprimant le providedIn: 'root' du décorateur @Injectable() et en les ajoutant au tableau providers: [] du composant. Le service sera disponible dans tous les composants enfants, les enfants de vue et les enfants de contenu. En plus des providers, vous pouvez ajouter un tableau viewProviders si vous souhaitez scoper le même token (avec une classe différente) uniquement à la vue du composant lui-même, par conséquent les enfants de contenu (ng-content) utiliseront le service du tableau providers défini en premier.
  • Platform services partagés entre plusieurs applications ou Angular Elements. Vous pouvez utiliser providedIn: 'platform' afin de rendre un service disponible entre plusieurs applications ou Angular Elements.
  • Singleton services peuvent être créés à l'aide de providedIn: 'root', de cette façon le service sera disponible à l'échelle de l'application en tant que singleton sans avoir besoin de l'ajouter au tableau providers d'un module comme c'était le cas dans les versions d'Angular inférieures à la v6.
  • Non singleton services pouvaient être créés en utilisant providedIn: 'any' afin de créer des services isolés (contrairement à un singleton) pour chaque injecteur chargé en lazy. Cette option est dépréciée depuis Angular 15, fournissez plutôt le service dans le composant ou la route qui a besoin de sa propre instance.

Configurer les dépendances

  • Ensuite, vous devez comprendre les différentes configurations d'injection que vous pouvez faire, en fait vous pouvez configurer l'injection avec différents types d'objets : une classe, un objet ou une simple valeur, une fabrique et même plus. Il vous sera obligatoire d'utiliser le mécanisme d'InjectionToken si le type n'a pas de représentation à l'exécution, par exemple une interface, sinon vous pouvez passer directement votre classe sans InjectionToken.
  • class: { provide: MyService, useClass: MyService } // Il est également possible d'utiliser un raccourci : MyService.
  • value: { provide: 'MY_CONST', useValue: 'https://angular.dev' } // 'MY_CONST' peut être déclaré comme une chaîne de caractères sans InjectionToken.
  • value: { provide: MY_CONST, useValue: 'https://angular.dev' } // MY_CONST peut être déclaré comme InjectionToken<string>.
  • value: { provide: MY_CONFIG, useValue: { value: 'https://angular.dev' } } // MY_CONFIG doit être déclaré comme InjectionToken<MyInterface> car une interface n'a pas de représentation à l'exécution.
  • factory: { provide: MY_OBS, deps: [DOCUMENT], useFactory: doThingFactory } // MY_OBS doit être déclaré comme InjectionToken<Observable<string>> et doThingFactory est une fonction qui renvoie l'observable. Vous pouvez également créer votre fabrique en utilisant le second argument d'InjectionToken. Veillez à bien comprendre la différence entre les deux : avec useFactory, ce n'est pas tree-shakable, vous devez déclarer le provider manuellement et vous pouvez facilement basculer entre différentes implémentations via une autre fonction useFactory. Avec l'option factory d'InjectionToken, c'est tree-shakable, le token est automatiquement fourni en root mais vous pouvez toujours modifier l'implémentation en déclarant un autre provider pour ce token dans un tableau providers.
  • existing: { provide: MY_TOKEN, useExisting: forwardRef(() => MyDirective) } // MY_TOKEN devient un alias de l'instance de MyDirective. En général, forwardRef est utilisé lorsqu'une classe est référencée avant sa définition et il permet aussi parfois de casser facilement une dépendance circulaire.

Décorer les dépendances

Les décorateurs ci-dessous peuvent être utilisés pour configurer plus précisément le comportement d'injection. Ils peuvent être utilisés dans la méthode constructeur ou dans le tableau deps d'une fabrique, et les mêmes options existent pour la fonction inject(), par exemple inject(MyService, { optional: true }).

  • par défaut : injecter sans décorateur, en remontant la hiérarchie des injecteurs...
  • self : injecter en utilisant uniquement le provider du composant lui-même (@Self() ou { self: true })
  • skipSelf : injecter en ignorant le provider du composant lui-même (@SkipSelf() ou { skipSelf: true })
  • optionnel : injecter s'il est fourni, sinon retourner null (@Optional() ou { optional: true })
  • host : injecter en regardant d'abord le composant lui-même et, s'il n'y est pas trouvé, chercher l'injecteur jusqu'à son composant hôte (@Host() ou { host: true }). Attention il existe des cas particuliers avec les directives et la projection de contenu.

En savoir plus sur la DI en Angular

En savoir plus sur l'injection de dépendances dans la documentation Angular officielle.

À suivre

En savoir plus sur Angular