Injection de dépendance angulaire expliquée avec des exemples

Qu'est-ce que l'injection de dépendance?

Motivation

L'injection de dépendance est souvent plus simplement appelée DI. Le paradigme existe dans tout Angular. Il garde le code flexible, testable et modifiable. Les classes peuvent hériter de la logique externe sans savoir comment la créer. Les consommateurs de ces classes n'ont pas non plus besoin de savoir quoi que ce soit.

DI évite aux classes et aux consommateurs d'avoir à en savoir plus que nécessaire. Pourtant, le code est aussi modulaire qu'avant grâce aux mécanismes supportant DI dans Angular.

Les services sont un bienfaiteur clé de l'ID. Ils s'appuient sur le paradigme de l' injection chez divers consommateurs. Ces consommateurs peuvent alors profiter de ce service et / ou le transférer ailleurs.

Le service n'est pas seul. Directives, tuyaux, composants, etc.: chaque schéma dans Angular bénéficie d'une manière ou d'une autre de DI.

Injecteurs

Les injecteurs sont des structures de données qui stockent des instructions détaillant où et comment les services se forment. Ils agissent comme des intermédiaires au sein du système Angular DI.

Les classes de module, de directive et de composant contiennent des métadonnées spécifiques aux injecteurs. Une nouvelle instance d'injecteur accompagne chacune de ces classes. De cette manière, l'arborescence des applications reflète sa hiérarchie d'injecteurs.

Les providers: []métadonnées acceptent les services qui s'enregistrent ensuite avec l'injecteur de la classe. Ce champ de fournisseur ajoute les instructions nécessaires au fonctionnement d'un injecteur. Une classe (en supposant qu'elle a des dépendances) instancie un service en prenant sa classe comme type de données. L'injecteur aligne ce type et crée une instance de ce service au nom de la classe.

Bien sûr, la classe ne peut instancier que ce pour quoi l'injecteur a des instructions. Si le propre injecteur de la classe n'a pas enregistré le service, il interroge son parent. Ainsi de suite et ainsi de suite jusqu'à atteindre un injecteur avec le service ou la racine de l'application.

Les services peuvent s'inscrire sur n'importe quel injecteur de l'application. Les services entrent dans le providers: []champ des métadonnées des modules de classe, des directives ou des composants. Les enfants de la classe peuvent instancier un service inscrit dans l'injecteur de la classe. Les injecteurs enfants se replient sur les injecteurs parents après tout.

Injection de dépendance

Jetez un œil aux squelettes de chaque classe: service, module, directive et composant.

// service import { Injectable } from '@angular/core'; @Injectable({ providedIn: /* injector goes here */ }) export class TemplateService { constructor() { } }
// module import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; @NgModule({ imports: [ CommonModule ], declarations: [], providers: [ /* services go here */ ] }) export class TemplateModule { }
// directive import { Directive } from '@angular/core'; @Directive({ selector: '[appTemplate]', providers: [ /* services go here */ ] }) export class TemplateDirective { constructor() { } }
//component import { Component } from '@angular/core'; @Component({ selector: 'app-template', templateUrl: './template.component.html', styleUrls: ['./template.component.css'], providers: [ /* services go here */ ] }) export class TemplateComponent { // class logic ... }

Chaque squelette peut enregistrer des services sur un injecteur. En fait, TemplateService est un service. Depuis Angular 6, les services peuvent désormais s'inscrire auprès d'injecteurs à l'aide de @Injectablemétadonnées.

Dans tout les cas

Notez les métadonnées providedIn: string( @Injectable) et providers: []( @Directive, @Componetet @Module). Ils indiquent aux injecteurs où et comment créer un service. Sinon, les injecteurs ne sauraient pas comment instancier.

Et si un service a des dépendances? Où iraient les résultats? Les fournisseurs répondent à ces questions afin que les injecteurs puissent instancier correctement.

Les injecteurs forment l'épine dorsale du cadre DI. Ils stockent des instructions pour instancier les services afin que les consommateurs n'aient pas à le faire. Ils reçoivent des instances de service sans avoir besoin de rien savoir sur la dépendance source!

Je dois également noter que d'autres schémas sans injecteurs peuvent toujours utiliser l'injection de dépendances. Ils ne peuvent pas enregistrer de services supplémentaires, mais ils peuvent toujours instancier à partir d'injecteurs.

Un service

Les providedIn: stringmétadonnées de @Injectablespécifient avec quel injecteur s'enregistrer. En utilisant cette méthode et selon que le service est utilisé, le service peut s'inscrire ou non auprès de l'injecteur. Angular appelle cela le tremblement des arbres .

Par défaut, la valeur est définie sur ‘root’. Cela se traduit par l'injecteur racine de l'application. Fondamentalement, définir le champ sur ‘root’rend le service disponible n'importe où.

Note rapide

Comme mentionné précédemment, les enfants injecteurs se replient sur leurs parents. Cette stratégie de repli garantit que les parents n'ont pas à se réinscrire pour chaque injecteur. Reportez-vous à cet article sur les services et les injecteurs pour une illustration de ce concept.

Les services enregistrés sont des singletons . Cela signifie que les instructions pour instancier le service existent sur un seul injecteur. Cela suppose qu'il n'a pas été explicitement enregistré ailleurs.

Module, directive et composant

Les modules et les composants ont chacun leur propre instance d'injecteur. Cela est évident compte tenu du providers: []champ de métadonnées. Ce champ prend un tableau de services et les enregistre avec l'injecteur du module ou de la classe de composant. Cette approche se passe dans les @NgModule, @Directiveou @Componentdécorateurs.

Cette stratégie omet le tremblement d'arbre ou la suppression facultative des services inutilisés des injecteurs. Les instances de service vivent sur leurs injecteurs pendant toute la durée de vie du module ou du composant.

Instancier des références

Les références au DOM peuvent être instanciées à partir de n'importe quelle classe. Gardez à l'esprit que les références sont toujours des services. Ils diffèrent des services traditionnels en ce qu'ils représentent l'état de quelque chose d'autre. Ces services incluent des fonctions pour interagir avec leur référence.

Directives are in constant need of DOM references. Directives perform mutations on their host elements through these references. See the following example. The directive’s injector instantiates a reference of the host element into the class’ constructor.

// directives/highlight.directive.ts import { Directive, ElementRef, Renderer2, Input } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { constructor( private renderer: Renderer2, private host: ElementRef ) { } @Input() set appHighlight (color: string) { this.renderer.setStyle(this.host.nativeElement, 'background-color', color); } }
// app.component.html 

Highlighted Text!

Renderer2 also gets instantiated. Which injector do these services come from? Well, each service’s source code comes from @angular/core. These services must then register with the application’s root injector.

import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { HighlightDirective } from './directives/highlight.directive'; @NgModule({ declarations: [ AppComponent, HighlightDirective ], imports: [ BrowserModule ], providers: [], bootstrap: [ AppComponent ] }) export class AppModule { }

An empty providers array!? Not to fear. Angular registers many services with the root injector automatically. This includes ElementRef and Renderer2. In this example, we are managing the host element through its interface stemming from the instantiation of ElementRef. Renderer2 lets us update the DOM through Angular’s view model.

You can read more about views from this article. They are the preferred method for DOM/view updates in Angular applications.

It is important recognize the role that injectors play in the above example. By declaring variable types in the constructor, the class obtains valuable services. Each parameter’s data type maps to a set of instructions within the injector. If the injector has that type, it returns an instance of said type.

Instantiating Services

The Services and Injectors article explains this section to an extent. Though, this section rehashes the previous section or the most part. Services will often provide references to something else. They may just as well provide an interface extending a class’ capabilities.

The next example will define a logging service that gets added to a component’s injector via its providers: [] metadata.

// services/logger.service.ts import { Injectable } from '@angular/core'; @Injectable() export class LoggerService { callStack: string[] = []; addLog(message: string): void { this.callStack = [message].concat(this.callStack); this.printHead(); } clear(): void { this.printLog(); this.callStack = []; console.log(“DELETED LOG”); } private printHead(): void  null);  private printLog(): void { this.callStack.reverse().forEach((log) => console.log(message)); } }
// app.component.ts import { Component } from '@angular/core'; import { LoggerService } from './services/logger.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', providers: [LoggerService] }) export class AppComponent { constructor(private logger: LoggerService) { } logMessage(event: any, message: string): void { event.preventDefault(); this.logger.addLog(`Message: ${message}`); } clearLog(): void { this.logger.clear(); } }
// app.component.html 

Log Example

SUBMIT

Delete Logged Messages

CLEAR

Focus on the AppComponent constructor and metadata. The component injector receives instructions from the provider’s metadata field containing LoggerService. The injector then knows what to instantiate LoggerService from requested in the constructor.

The constructor parameter loggerService has the type LoggerService which the injector recognizes. The injector follows through with the instantiation as mentioned.

Conclusion

Dependency injection (DI) is a paradigm. The way it works in Angular is through a hierarchy of injectors. A class receives its resources without having to create or know about them. Injectors receive instruction and instantiate a service depending on which one was requested.

DI shows up a lot in Angular. The official Angular documentation explains why the paradigm is so prevalent. They also go on to describe the numerous use-cases for DI in Angular way beyond what was discussed in this article. Check it out by clicking below!

More on dependency injection:

  • Intro to Angular dependency injection
  • Quick intro to dependency injection