Injection de dépendance > Injection de dépendance avec Angular

Références

L'actualité

Librairie

L'information

Injection de dépendance avec Angular

L'injection de dépendance (ID) est un modèle de conception d'application important. Angular possède son propre cadre DI, qui est généralement utilisé dans la conception d'applications Angular pour accroître leur efficacité et leur modularité.

Les dépendances sont des services ou des objets dont une classe a besoin pour remplir sa fonction. DI est un modèle de codage dans lequel une classe demande des dépendances de sources externes plutôt que de les créer elle-même.

Dans Angular, l'infrastructure DI fournit des dépendances déclarées à une classe lorsque cette classe est instanciée. Ce guide explique le fonctionnement de DI dans Angular et explique comment vous l'utilisez pour rendre vos applications souples, efficaces et robustes, ainsi que pour qu'elles puissent être testées et maintenues.

Commencez par passer en revue cette version simplifiée de la fonction héros du Tour of Heroes. Cette version simple n'utilise pas DI; nous allons le convertir pour le faire.
heroes.component.ts

hero-list.component.ts

hero.ts

mock-heroes.ts

HeroesComponent est la composante des héros de haut niveau. Son seul but est d'afficher HeroListComponent, ce qui affiche une liste de noms de héros.

Cette version de HeroListComponent obtient les héros du tableau HEROES, une collection en mémoire définie dans un fichier mock-heroes séparé.

Cette approche fonctionne pour le prototypage, mais n'est pas robuste ni maintenable. Dès que vous essayez de tester ce composant ou d'obtenir des héros d'un serveur distant, vous devez modifier la mise en oeuvre HeroesListComponent et remplacer chaque utilisation des données HEROES fictives.

Créer et enregistrer un service injectable

L'infrastructure DI vous permet de fournir des données à un composant à partir d'une classe de service injectable, définie dans son propre fichier. Pour démontrer, nous allons créer une classe de service injectable qui fournit une liste de héros et enregistrer cette classe en tant que fournisseur de ce service.

Avoir plusieurs classes dans le même fichier peut être déroutant. Nous vous recommandons généralement de définir les composants et les services dans des fichiers séparés.

Si vous combinez un composant et un service dans le même fichier, il est important de définir d'abord le service, puis le composant. Si vous définissez le composant avant le service, vous obtenez une erreur de référence null au moment de l'exécution.

Créer un classe de service injectable

La CLI Angular peut générer une nouvelle classe HeroService dans le src/app/heroes dossier avec cette commande.

La commande crée le squelette HeroService suivant.

@Injectable() est un ingrédient essentiel dans chaque définition de service Angular. Le reste de la classe a été écrit pour exposer une méthode qui renvoie les mêmes données fictives qu'auparavant. Une application réelle obtiendrait probablement ses données de manière asynchrone à partir d'un serveur distant, mais nous l'ignorerons pour nous concentrer sur les mécanismes d'injection du service.


Configurer un injecteur avec un fournisseur de service

La classe que nous avons créée fournit un service. Le décorateur @Injectable() le marque comme un service pouvant être injecté, mais Angular ne peut l'injecter nulle part tant que vous n'avez pas configuré un injecteur de dépendance Angular avec un fournisseur de ce service.

L'injecteur est responsable de la création des instances de service et de leur injection dans des classes telles que HeroListComponent. Vous créez rarement un injecteur Angular vous-même. Angular crée des injecteurs pour vous lors de l'exécution de l'application, en commençant par l'injecteur racine créé lors du processus d'amorçage.

Un fournisseur indique à un injecteur comment créer le service. Vous devez configurer un injecteur avec un fournisseur avant que cet injecteur puisse créer un service (ou fournir un autre type de dépendance).

Un fournisseur peut être la classe de service elle-même, afin que l'injecteur puisse l'utiliser pour créer une nouvelle instance. Vous pouvez également définir plusieurs classes pour fournir le même service de différentes manières et configurer différents injecteurs avec différents fournisseurs.

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 de le résoudre.

Vous pouvez configurer des injecteurs avec des fournisseurs à différents niveaux de votre application, en définissant une valeur de métadonnées dans l'un des trois emplacements suivants :

  • Dans le décorateur @Injectable() pour le service lui-même.
  • Dans le décorateur @NgModule() pour un NgModule.
  • Dans le décorateur @Component() pour un composant.
Le décorateur @Injectable() a l'option de métadonnées providedIn, dans laquelle vous pouvez spécifier le fournisseur de la classe de service décorée avec l'injecteur root ou avec l'injecteur pour un NgModule spécifique.

Les décorateur @NgModule() et @Component() ont l'option de métadonnées providers, dans laquelle vous pouvez configurer des fournisseurs pour les injecteurs de niveau NgModule ou de composant.

Les composants sont des directives et l'option providers est héritée de @Directive(). Vous pouvez également configurer des fournisseurs pour les directives et les tubes au même niveau que le composant.

Services injectés

Pour obtenir HeroListComponent des héros de HeroService, il doit demander à HeroService d'être injecté, plutôt que de créer sa propre instance de HeroService avec la commande new.

Vous pouvez dire à Angular d'injecter une dépendance dans le constructeur d'un composant en spécifiant un paramètre de constructeur avec le type de dépendance. Voici le constructeur de HeroListComponent qui demande l'injection de HeroService.

Bien sûr, HeroListComponent devrait faire quelque chose avec l'injecté HeroService. Voici le composant révisé, qui utilise le service injecté, côte à côte avec la version précédente à des fins de comparaison.

hero-list.component (avec DI)

hero-list.component (sans DI)

HeroService doit être fourni dans un injecteur parent. Le code dans le Component HeroList ne dépend pas d'où vient HeroService. Si vous avez décidé de fournir dans HeroService AppModule, HeroListComponent ne changerait pas.

Hiérarchie des injecteurs et instances de service

Les services sont des singletons dans le cadre d'un injecteur. C'est-à-dire qu'il y a au plus une instance d'un service dans un injecteur donné. Il n'y a qu'un seul injecteur de racine pour une application. Fournir UserService au niveau root ou AppModule signifie qu'il est enregistré avec l'injecteur de racine. Il n'y a qu'une seule instance UserService dans l'application entière et chaque classe injectée UserService obtient cette instance de service, sauf si vous configurez un autre fournisseur avec un injecteur enfant.

Angular DI possède un système d'injection hiérarchique, ce qui signifie que les injecteurs imbriqués peuvent créer leurs propres instances de service. Angular crée régulièrement des injecteurs imbriqués. Chaque fois que Angular crée une nouvelle instance d'un composant providersspécifié dans, il crée également un nouvel injecteur enfant pour cette instance. De même, lorsqu'un nouveau NgModule est chargé paresseux au moment de l'exécution, Angular peut créer un injecteur avec ses propres fournisseurs @Component()

Les modules enfants et les injecteurs de composants sont indépendants les uns des autres et créent leurs propres instances distinctes des services fournis. Lorsque Angular détruit une instance de NgModule ou de composant, il détruit également cet injecteur et ses instances de service.

Grâce à l'héritage des injecteurs , vous pouvez toujours injecter des services à l'échelle de l'application dans ces composants. L'injecteur d'un composant est un enfant de l'injecteur de son composant parent, un descendant de l'injecteur du parent, et ainsi de suite jusqu'à l' injecteur racine de l'application. Angular peut injecter un service fourni par n'importe quel injecteur de cette lignée.

Par exemple, Angular peut injecter HeroListComponent à la fois le HeroService fourni HeroComponent et le UserService fourni AppModule.

Test des composants avec des dépendance

Concevoir une classe avec injection de dépendance rend la classe plus facile à tester. Pour répertorier les dépendances en tant que paramètres de constructeur, il vous suffira peut-être de tester efficacement les composants de l'application.

Par exemple, vous pouvez créer un nouveau service Component HeroList avec un service fictif que vous pouvez manipuler lors du test.



Services nécessitant d'autres services

Le service peut avoir ses propres dépendances. HeroService est très simple et n'a pas de dépendances propres. Supposons toutefois que vous souhaitiez qu'il signale ses activités via un service de journalisation. Vous pouvez appliquer le même schéma d'injection de constructeur en ajoutant un constructeur prenant un paramètre Logger.

Voici la version révisée de HeroService qui injecte Logger, côte à côte avec le service précédent, à des fins de comparaison.

src/app/heroes/hero.service (v2)

src/app/heroes/hero.service (v1)

src/app/logger.service

Le constructeur demande une instance injectée de Logger et la stocke dans un champ privé appelé logger. La méthode getHeroes() enregistre un message lorsqu'il est demandé de récupérer des héros.

Notez que le service Logger a également le décorateur @Injectable(), même s'il n'a peut-être pas besoin de ses propres dépendances. En fait, le décorateur @Injectable() est requis pour tous les services.

Lorsque Angular crée une classe dont le constructeur a des paramètres, il recherche des métadonnées de type et d'injection sur ces paramètres afin d'injecter le bon service. Si Angular ne trouve pas cette information de paramètre, une erreur est renvoyée. Angular ne peut trouver les informations de paramètre que si la classe a un décorateur quelconque. Le décorateur @Injectable() est le décorateur standard pour les classes de service.

Jetons d'injection de dépendance

Lorsque vous configurez un injecteur avec un fournisseur, vous associez ce fournisseur à un jeton DI. L'injecteur gère un mappage interne de fournisseur de jetons auquel il fait référence lorsqu'il est invité à définir une dépendance. Le jeton est la clé de la carte.

Dans des exemples simples, la valeur de dépendance est une instance et le type de classe constitue sa propre clé de recherche. Ici, vous obtenez un HeroService directement de l'injecteur en fournissant le type HeroService en tant que jeton:

Le comportement est similaire lorsque vous écrivez un constructeur qui nécessite une dépendance injectée basée sur la classe. Lorsque vous définissez un paramètre de constructeur avec le type HeroService de classe, Angular sait comment injecter le service associé à ce jeton HeroService de classe :

De nombreuses valeurs de dépendance sont fournies par les classes, mais pas toutes.

Dépendances optionnel

HeroService nécessite un enregistreur, mais si cela pouvait se passer sans un ?

Lorsqu'un composant ou un service déclare une dépendance, le constructeur de la classe prend cette dépendance en tant que paramètre. Vous pouvez indiquer à Angular que la dépendance est facultative en annotant le paramètre constructeur avec @Optional().
Lors de l'utilisation @Optional(), votre code doit être préparé pour une valeur null. Si vous n'enregistrez aucun fournisseur de journalisation, l'injecteur définit la valeur du logger sur null. @Inject() et @Optional() sont des décorateurs de paramètres. Ils modifient la manière dont l'infrastructure DI fournit une dépendance, en annotant le paramètre de dépendance sur le constructeur de la classe qui nécessite la dépendance.