Injection de dépendance > Injecteurs hiérarchiques

Références

L'actualité

Librairie

L'information

Injecteurs de dépendance hiérarchique

Le système d'injection de dépendance Angular est hiérarchique. Il existe une arborescence d'injecteurs parallèle à l'arborescence des composants d'une application. Vous pouvez reconfigurer les injecteurs à n'importe quel niveau de cette arborescence.

Où configurer les fournisseurs

Vous pouvez configurer des fournisseurs pour différents injecteurs dans la hiérarchie des injecteurs. Un injecteur interne au niveau de la plateforme est partagé par toutes les applications en cours d'exécution. L'injecteur AppModule est la racine d'une hiérarchie d'injecteurs au niveau de l'application et, au sein d'un NgModule, les injecteurs au niveau des directives suivent la structure de la hiérarchie des composants.

Les choix que vous faites concernant l'emplacement de configuration des fournisseurs entraînent des différences dans la taille finale de l'ensemble, l'étendue et la durée de vie du service.

Lorsque vous spécifiez des fournisseurs dans le décorateur @Injectable() du service lui-même (généralement au niveau de la racine de l'application), les outils d'optimisation tels que ceux utilisés par les versions de production de la CLI peuvent effectuer un tremblement d'arbre, ce qui supprime les services qui ne sont pas utilisés par votre application. La secousse des arbres réduit la taille des faisceaux.

Vous êtes susceptible d'injecter UserService à de nombreux endroits dans l'application et voudrez injecter la même instance de service à chaque fois. Fournir UserService via l'injecteur root est un bon choix. Il s'agit du paramètre par défaut utilisé par la CLI Angular lorsque vous générez un service pour votre application.

Injecteur de plate-forme

Lorsque vous utilisez providedIn:'root', vous configurez l'injecteur de racine pour l'application , qui est l'injecteur AppModule. La racine réelle de toute la hiérarchie des injecteurs est un injecteur de plate-forme qui est le parent des injecteurs de racine d'application. Cela permet à plusieurs applications de partager une configuration de plate-forme. Par exemple, un navigateur ne possède qu'une seule barre d'URL, quel que soit le nombre d'applications que vous exécutez.

L'injecteur de plate-forme est utilisé en interne lors du démarrage, pour configurer des dépendances spécifiques à la plate-forme. Vous pouvez configurer des fournisseurs supplémentaires spécifiques à la plate-forme au niveau de la plate-forme en extraProviders utilisant la fonction platformBrowser().

Les fournisseurs de niveau NgModule peuvent être spécifiés avec l'option de métadonnées des fournisseurs @NgModule() ou avec l'option @Injectable() fourni (avec certains modules autres que l'AppModule racine).

Utilisez l'option @NgModule() fournit si un module est chargé paresseux. L'injecteur du module est configuré avec le fournisseur lorsque ce module est chargé, et Angular peut injecter les services correspondants dans n'importe quelle classe créée dans ce module. Si vous utilisez l'option @Injectable() fournie dans: MyLazyloadModule, le fournisseur peut être modifié au moment de la compilation, s'il n'est utilisé nulle part ailleurs dans l'application.

Pour les injecteurs de niveau racine et de niveau module, une instance de service vit toute la vie de l'application ou du module, et Angular injecte cette instance de service dans chaque classe qui en a besoin.

Les fournisseurs de niveau composant configurent l'injecteur de chaque instance de composant. Angular peut uniquement injecter les services correspondants dans cette instance de composant ou l'une de ses instances de composant descendant. Angular ne peut pas injecter la même instance de service ailleurs.

Un service fourni par composant peut avoir une durée de vie limitée. Chaque nouvelle instance du composant obtient sa propre instance du service. Lorsque l'instance de composant est détruite, cette instance de service l'est également.

Dans notre exemple d'application, HeroComponent est créé au démarrage de l'application et n'est jamais détruit. L'instance HeroService créée pour HeroComponent est donc conservée pendant toute la vie de l'application. Si vous souhaitez restreindre l'accès de HeroService à HeroComponent et à son composant imbriqué HeroListComponent, fournissez HeroService au niveau du composant, dans les métadonnées HeroComponent.

@Configuration de niveau injectable

Le décorateur @Injectable() identifie chaque classe de service. L'option de métadonnées fourni pour une classe de service configure un injecteur spécifique pour utiliser la classe décorée en tant que fournisseur du service. Lorsqu'une classe injectable fournit son propre service à l'injecteur racine, le service est disponible partout où la classe est importée.

L'exemple suivant configure un fournisseur pour HeroService à l'aide du décorateur @Injectable() de la classe.

Cette configuration indique à Angular que l'injecteur racine de l'application est responsable de la création d'une instance de HeroService en appelant son constructeur et de la mise à disposition de cette instance dans l'application.

Fournir un service avec l'injecteur racine de l'application est un cas typique, et la CLI configure automatiquement ce type de fournisseur pour vous lors de la génération d'un nouveau service. Cependant, vous ne voudrez peut-être pas toujours fournir votre service au niveau racine. Vous pouvez, par exemple, souhaiter que les utilisateurs choisissent explicitement d'utiliser le service.

Au lieu de spécifier l'injecteur de racine, vous pouvez définir fourni à un NgModule spécifique.

Par exemple, dans l'extrait suivant, le décorateur @Injectable() configure un fournisseur disponible dans tout injecteur incluant le module HeroModule.

Cela n'est généralement pas différent de la configuration de l'injecteur du NgModule lui-même, sauf que le service peut être utilisé dans les arbres si le NgModule ne l'utilise pas. Cela peut être utile pour une bibliothèque qui propose un service particulier que certains composants pourraient vouloir injecter de manière optionnelle, et laisse à l'application le soin de fournir le service.

@Injecteurs de niveau NgModule

Vous pouvez configurer un fournisseur au niveau du module en utilisant l'option de métadonnées providedIn pour un module Ng non-racine, afin de limiter la portée du fournisseur à ce module. Cela équivaut à spécifier le module non racine dans les métadonnées @Injectable(), sauf que le service fourni de cette façon n'est pas arborant.

En règle générale, vous n'avez pas besoin de spécifier AppModule avec providedIn, car l'injecteur racine de l'application est l'injecteur AppModule. Toutefois, si vous configurez un fournisseur d'application dans les métadonnées @NgModule() pour AppModule, il remplace celui configuré pour root dans les métadonnées @Injectable(). Pour ce faire, vous pouvez configurer un fournisseur autre que celui par défaut d'un service partagé avec plusieurs applications.

Voici un exemple de cas où la configuration du routeur de composant inclut une stratégie d'emplacement autre que celle par défaut en répertoriant son fournisseur dans la liste des fournisseurs de l'AppModule.


@Injecteurs au niveau composant

Les composants individuels dans un NgModule ont leurs propres injecteurs. Vous pouvez limiter la portée d'un fournisseur à un composant et à ses enfants en configurant le fournisseur au niveau du composant à l'aide des métadonnées @Component

L'exemple suivant est une révision HeroesComponent qui spécifie dans HeroService son tableau providers. HeroService peut fournir des héros aux instances de ce composant ou à toute instance de composant enfant.


Élément injecteurs

Un injecteur n'appartient pas réellement à un composant, mais plutôt à l'élément d'ancrage de l'occurrence de composant dans le DOM. Une instance de composant différente sur un élément DOM différent utilise un injecteur différent.

Les composants sont un type spécial de directive et la propriété providers de @Component() est héritée de @Directive(). Les directives peuvent également avoir des dépendances et vous pouvez configurer les fournisseurs dans leurs métadonnées @Directive(). Lorsque vous configurez un fournisseur pour un composant ou une directive à l'aide de la propriété providers, ce fournisseur appartient à l'injecteur de l'élément DOM d'ancrage. Les composants et les directives sur le même élément partagent un injecteur.

Injecteur bouillonnant

Considérez la variation de ce guide sur l'application Tour of Heroes. En haut se trouve AppComponent, qui comporte des sous-composants, tels que HeroesListComponent. HeroesListComponent contient et gère plusieurs instances de HeroTaxReturnComponent. Le diagramme suivant représente l'état de cette arborescence de composants à trois niveaux lorsque trois instances de HeroTaxReturnComponent sont ouvertes simultanément.


Lorsqu'un composant demande une dépendance, Angular tente de satisfaire cette dépendance avec un fournisseur inscrit dans le propre injecteur de ce composant. Si l'injecteur du composant manque de fournisseur, il transmet la demande à l'injecteur de son composant parent. Si cet injecteur ne peut pas satisfaire la demande, il transmet la demande au prochain injecteur parent dans l'arborescence. Les demandes continuent de bouillonner jusqu'à ce que Angular trouve un injecteur capable de gérer la demande ou d'épuisement des injecteurs ancêtres. Si elle manque d'ancêtres, Angular génère une erreur.

Si vous avez enregistré un fournisseur pour le même jeton DI à différents niveaux, la première rencontre angulaire est celle qu'il utilise pour fournir la dépendance. Si, par exemple, un fournisseur est enregistré localement dans le composant nécessitant un service, Angular ne recherche pas un autre fournisseur du même service.

Vous pouvez limiter le bouillonnement en ajoutant le décorateur de paramètres @Host() au paramètre service dépendant du constructeur d'un composant. La recherche de fournisseurs s'arrête au niveau de l'injecteur pour l'élément hôte du composant.

  • Voir un exemple d'utilisation de @Host avec @Optional, un autre décorateur de paramètres qui vous permet de gérer le cas null si aucun fournisseur n'est trouvé.
  • En savoir plus sur le décorateur @Host et les injecteurs d'éléments.


Si vous enregistrez uniquement les fournisseurs avec l'injecteur racine au niveau supérieur (généralement le module racine AppModule), l'arborescence des injecteurs semble être plate. Toutes les demandes sont transmises à l'injecteur racine, que vous l'ayez configuré avec la méthode bootstrapModule ou enregistré tous les fournisseurs avec la racine dans leurs propres services.

Composants injecteurs

La possibilité de configurer un ou plusieurs fournisseurs à différents niveaux ouvre des possibilités intéressantes et utiles.

Scénario: isolation de service

Des raisons architecturales peuvent vous amener à restreindre l'accès à un service dans le domaine d'application auquel il appartient. Par exemple, l'exemple de guide comprend un VillainsListComponent qui affiche une liste des méchants. Il obtient ces méchants d'un VillainsService.

Si vous fournissez VillainsService à la racine AppModule (où vous avez enregistré le fichier HeroesService), cela le rendrait VillainsService disponible partout dans l'application, y compris les flux de travail Hero. Si vous modifiez ensuite le VillainsService, vous pouvez casser quelque chose quelque part dans un composant héros. Fournir le service dans la racine AppModule crée ce risque.

Au lieu de cela, vous pouvez fournir les VillainsService aux métadonnées providers de VillainsListComponent suivantes :

En fournissant VillainsService dans les métadonnées VillainsListComponent et nulle part ailleurs, le service devient disponible uniquement dans l'arborescence VillainsListComponent et ses sous-composants. C'est toujours un singleton, mais c'est un singleton qui existe uniquement dans le domaine des méchants.

Maintenant, vous savez qu'un composant héros ne peut pas y accéder. Vous avez réduit votre risque d'erreur.

Scénario: plusieurs sessions de montage

De nombreuses applications permettent aux utilisateurs de travailler sur plusieurs tâches ouvertes en même temps. Par exemple, dans une application de préparation de taxe, le préparateur peut travailler sur plusieurs déclarations de revenus, en passant de l'une à l'autre tout au long de la journée.

Ce guide illustre ce scénario. Imaginez un extérieur HeroListComponent qui affiche une liste de héros.

Pour ouvrir la déclaration de revenus d'un héros, le préparateur clique sur un nom de héros, ce qui ouvre un composant pour l'édition de cette déclaration. Chaque déclaration de revenus de héros sélectionnée s'ouvre dans son propre composant et plusieurs déclarations peuvent être ouvertes en même temps.

Chaque composant de la déclaration de revenus présente les caractéristiques suivantes:

  • Est-ce sa propre session d'édition de déclaration d'impôt ?
  • Peut changer une déclaration de revenus sans affecter une déclaration dans un autre composant.
  • Peut enregistrer les modifications apportées à sa déclaration de revenus ou les annuler.


Supposons que le HeroTaxReturnComponent a une logique pour gérer et restaurer les changements. Ce serait une tâche assez facile pour une simple déclaration de revenus de héros. Dans le monde réel, avec un modèle de données de déclaration de revenus riche, la gestion du changement serait délicate. Vous pouvez déléguer cette gestion à un service d'assistance, comme le montre cet exemple.

Voici le HeroTaxReturnService. Il en cache un HeroTaxReturn, suit les modifications apportées à ce retour et peut le sauvegarder ou le restaurer. Il délègue également au singleton d'application HeroService, qu'il obtient par injection.

Voici le HeroTaxReturnComponent qui s'en sert.

La déclaration de taxe à éditer arrive via la propriété input qui est implémentée avec les getters et les setters. Le configurateur initialise propre l'instance du composant HeroTaxReturnService avec le retour entrant. Le getter renvoie toujours ce que le service dit être l'état actuel du héros. Le composant demande également au service de sauvegarder et de restaurer cette déclaration de revenus.

Cela ne fonctionnera pas si le service est un singleton à l'échelle de l'application. Chaque composant partagerait la même instance de service et chaque composant écraserait la déclaration de revenus ayant appartenu à un autre héros.

Pour éviter cela, nous configurons l'injecteur au niveau composant de HeroTaxReturnComponent fournir le service, à l'aide de la propriété providers dans les métadonnées du composant.

Le HeroTaxReturnComponent a son propre fournisseur de HeroTaxReturnService. Rappelez-vous que chaque instance de composant a son propre injecteur. La fourniture du service au niveau du composant garantit que chaque instance du composant obtient sa propre instance privée du service et qu'aucune déclaration de revenus n'est remplacée.

Scénario: prestataires spécialisés

Une autre raison de redonner un service à un autre niveau est de remplacer une implémentation plus spécialisée de ce service, plus en profondeur dans l'arborescence des composants.

Considérons un composant Car qui dépend de plusieurs services. Supposons que vous ayez configuré l'injecteur racine (marqué A) avec des fournisseurs génériques pour CarService, EngineService et TiresService.

Vous créez un composant de voiture (A) qui affiche une voiture construite à partir de ces trois services génériques.

Ensuite, vous créez un composant enfant (B) définissant ses propres fournisseurs spécialisés CarService et EngineService disposant de fonctionnalités spéciales adaptées à tout ce qui se passe dans le composant (B).

Le composant (B) est le parent d'un autre composant (C) qui définit son propre fournisseur encore plus spécialisé CarService.


En coulisse, chaque composant configure son propre injecteur avec zéro, un ou plusieurs fournisseurs définis pour ce composant lui-même.

Lorsque vous résolvez une instance du Car composant le plus profond (C), son injecteur génère une instance Car résolue par l'injecteur (C) avec une résolution Engine par l'injecteur (B) et celle Tires résolue par l'injecteur de racine (A).