Injection de dépendance en angulaire

Explorez l’injection de dépendance dans un modèle de conception angulaire – qui permet à un objet de recevoir ses dépendances d’une source externe plutôt que de les créer elle-même.
Angulaireun cadre puissant et d’opinion pour créer des applications à une seule page dynamique, fournit aux développeurs un environnement structuré pour créer un code frontal maintenable et évolutif. Son architecture encourage les concepts comme le développement axé sur les composants, activer le contrôle expressif de modèle, Suivi de l’état réactif et Gestion efficace des donnéesentre autres avantages.
Dans cet article, nous explorerons l’injection de dépendance – un concept de base qui simplifie la gestion des dépendances, améliore la modularité et améliore la testabilité des applications angulaires.
Le problème: gérer les dépendances sans DI
Imaginez un scénario où nous créons une grande application Web avec plusieurs composants qui reposent sur la logique partagée. Par exemple, considérez un composant de réception qui doit calculer les coûts totaux en réutilisant un utilitaire de calculatrice existant. Sans mécanisme formel comme l’injection de dépendance, nous pourrions écrire du code comme ceci:
class ReceiptComponent {
private calculator: Calculator;
totalCost: number;
constructor() {
this.calculator = new Calculator();
this.totalCost = this.calculator.add(50, 25);
}
}
class Calculator {
add(x: number, y: number): number {
return x + y;
}
}
Dans l’exemple de code ci-dessus, le ReceiptComponent
Instancie directement le Calculator
classe dans son constructeur. Cette approche vide la dépendance, ce qui signifie que le composant est responsable création et gérant le Calculator
plutôt que de simplement compter sur lui pour remplir sa fonction.
Bien que cela fonctionne pour des applications à petite échelle, certains problèmes peuvent survenir à mesure que la base de code se développe:
- Couplage serré – Le
ReceiptComponent
est étroitement couplé auCalculator
classe, ce qui rend difficile de remplacer ou d’étendre la fonctionnalité de la calculatrice sans modifier le composant. - Manque de testabilité – tester le
ReceiptComponent
isolément devient difficile car nous ne pouvons pas facilement se moquer duCalculator
classe. Se moquer ou le remplacer par un double test n’est pas simple car leReceiptComponent
gère directement l’instanciation. - Duplication de code – si plusieurs composants nécessitent le
Calculator
chacun créera sa propre instance, gaspillant les ressources et rendant la maintenance plus lourde.
Idéalement, nous serions se découpler le ReceiptComponent
de Calculator
permettant au composant de déclarer son besoin d’une calculatrice sans gérer sa création. C’est là que injection de dépendance entre en jeu.
Comprendre l’injection de dépendance
Injection de dépendance (DI) est un modèle de conception qui permet à un objet de recevoir ses dépendances d’une source externe plutôt que de les créer elle-même. L’idée centrale est de séparer la construction d’objets de son comportement, favorisant la flexibilité, la testabilité et la réutilisabilité.
Dans le contexte de DI, il y a trois rôles clés:
- Fournisseur de dépendance – fournit les dépendances (par exemple, une classe de service ou une fonction d’usine)
- Consommateur de dépendance – l’objet qui nécessite la dépendance (par exemple, un composant)
- Injecteur – Le mécanisme qui relie les fournisseurs et les consommateurs en livrant des dépendances où ils sont nécessaires
En utilisant DI, notre exemple précédent peut être refactorisé pour découpler le ReceiptComponent
de Calculator
:
class ReceiptComponent {
totalCost: number;
constructor(private calculator: Calculator) {
this.totalCost = this.calculator.add(50, 25);
}
}
class Calculator {
add(x: number, y: number): number {
return x + y;
}
}
Dans cette configuration, le Calculator
n’est plus instancié directement dans le ReceiptComponent
. Au lieu de cela, il est fourni en externe via le constructeur. Le constructor(private calculator: Calculator)
La syntaxe déclare la dépendance et permet à un injecteur externe de fournir le Calculator
exemple. Cette approche apporte plusieurs avantages:
- Flexibilité – Le
ReceiptComponent
peut fonctionner avec n’importe quel objet adhérant auCalculator
Interface, simplifiant les remplacements ou les extensions. - Testabilité – Le
Calculator
peut facilement être moqué pendant les tests, permettant leReceiptComponent
à tester indépendamment. - Réutilisabilité – un seul
Calculator
L’instance peut être partagée sur plusieurs composants, réduisant la redondance et améliorant l’efficacité.
En déchargeant la responsabilité de fournir des dépendances, le ReceiptComponent
se concentre uniquement sur sa fonctionnalité, entraînant une architecture plus propre et plus maintenable.
Injection de dépendance en angulaire
Le système DI d’Angular s’appuie sur ce concept, fournissant un cadre robuste pour gérer les dépendances. Cela permet une modularité, une efficacité et une flexibilité cohérentes dans la conception des applications.
Services en Angular
Les services sont au cœur du système DI d’Angular. Ce sont des éléments de logique réutilisables qui peuvent être injectés en composants, directives et autres services. Angular fournit le @Injectable Décorateur pour marquer une classe en tant que service qui peut participer au système DI.
Voici un exemple de service simple:
import { Injectable } from "@angular/core";
@Injectable({ providedIn: "root" })
export class CalculatorService {
add(x: number, y: number): number {
return x + y;
}
}
Le providedIn: 'root'
Les métadonnées signifient que le service est disponible tout au long de l’application en tant que singleton. Donc une seule instance du CalculatorService
sera partagé sur tous les composants et services qui l’injectent.
Injection de services dans les composants
Pour utiliser un service dans un composant, nous l’injectons via le constructeur du composant. Angular fournit automatiquement l’instance de service requise:
import { Component } from "@angular/core";
import { CalculatorService } from "./calculator.service";
@Component({
selector: "app-receipt",
template: "<h1>The total is {{ totalCost }}</h1>",
})
export class ReceiptComponent {
totalCost: number;
constructor(private calculator: CalculatorService) {
this.totalCost = this.calculator.add(50, 25);
}
}
Dans l’exemple ci-dessus, le système DI d’Angular résout la dépendance et injecte l’instance partagée de CalculatorService
dans le ReceiptComponent
. Cette approche maintient le composant axé sur ses fonctionnalités tout en déléguant la logique comme des calculs au service.
Injection de dépendance au niveau des composants
Alternativement, Angular nous permet d’étendre les services à des composants spécifiques en utilisant le fournisseurs champ dans le @Component
décorateur. Cette approche signifie qu’une nouvelle instance du service est créée pour chaque instance du composant:
import { Component } from "@angular/core";
import { CalculatorService } from "./calculator.service";
@Component({
selector: "app-receipt",
template: "<h1>The total is {{ totalCost }}</h1>",
providers: [CalculatorService],
})
export class ReceiptComponent {
totalCost: number;
constructor(private calculator: CalculatorService) {
this.totalCost = this.calculator.add(50, 25);
}
}
Lorsqu’il est fourni au niveau du composant, le CalculatorService
est limité au ReceiptComponent
et ses composants enfants. Chaque nouvelle instance de ReceiptComponent
recevra sa propre instance isolée du service.
Le choix entre providedIn: 'root'
et les fournisseurs de niveau composant dépendent du cas d’utilisation:
providedIn: 'root'
est préféré pour les services apatrides ou partagés à l’échelle mondiale, tels que les services publics de journalisation ou les clients HTTP, car il réduit la duplication et exploite la partage d’arbres pour les services inutilisés.- Les fournisseurs de niveau composant sont idéaux lorsque le service doit maintenir l’état spécifique à une instance de composante ou lorsque l’isolement entre les composants est requis.
En général, en utilisant providedIn: 'root'
est l’approche par défaut et la plus efficace pour les services mondiaux, tandis que les fournisseurs au niveau des composants offrent une flexibilité pour des cas d’utilisation plus spécifiques.
Configuration plus avancée
Le système DI d’Angular prend en charge les configurations avancées pour aborder des scénarios complexes. Par exemple, nous pouvons utiliser fournisseurs d’usine Pour configurer dynamiquement le comportement du service en fonction des données d’exécution ou d’autres dépendances.
Voici un exemple de service, ConfigurableService
qui accepte un point de terminaison de l’API comme dépendance via son constructeur:
import { Injectable } from "@angular/core";
@Injectable()
export class ConfigurableService {
constructor(private apiEndpoint: string) {}
getData() {
return `Fetching data from ${this.apiEndpoint}`;
}
}
Pour fournir dynamiquement le point de terminaison de l’API requis au moment de l’exécution, nous pouvons utiliser un Injection et un fournisseur d’usine pour fournir la valeur:
import { InjectionToken, NgModule } from "@angular/core";
const API_ENDPOINT = new InjectionToken<string>("API_ENDPOINT");
const configurableServiceFactory = (endpoint: string) =>
new ConfigurableService(endpoint);
@NgModule({
providers: [
{ provide: API_ENDPOINT, useValue: "https://api.example.com" },
{
provide: ConfigurableService,
useFactory: configurableServiceFactory,
deps: [API_ENDPOINT],
},
],
})
export class AppModule {}
Dans le code ci-dessus, le système DI d’Angular montre comment configurer un service dynamiquement à l’aide d’un InjectionToken
un fournisseur d’usine et des valeurs d’exécution. Le ConfigurableService
s’appuie sur le API_ENDPOINT
Jeton pour recevoir sa dépendance. Angular résout ce jeton à la valeur https://api.example.com
et le transmet à la fonction d’usine, qui construit et fournit l’instance de service.
Cette approche permet au service de s’adapter aux configurations d’exécution, telles que les paramètres spécifiques à l’environnement tout en maintenant le code modulaire et maintenable.
Les fournisseurs d’usine ne sont qu’un exemple de configuration avancée dans le système DI d’Angular. Le DI d’Angular prend également en charge d’autres configurations avancées, telles que injecteurs hiérarchiques Pour les services de cadrage à des modules ou composants spécifiques, multi-procureurs pour enregistrer plusieurs implémentations du même jeton, et contextes d’injection pour la création de services dynamiquement lors d’événements de cycle de vie spécifiques. Ces configurations permettent aux développeurs de régler la gestion de la dépendance pour s’adapter aux architectures d’application complexes et à fournir une modularité et une flexibilité si nécessaire.
Conclure
Le système d’injection de dépendance d’Angular est la pierre angulaire de son cadre, permettant aux développeurs de construire des applications flexibles, maintenables et testables. En comprenant comment DI fonctionne et en tirant parti des fonctionnalités d’Angular, nous pouvons écrire du code plus propre et créer des architectures plus évolutives.
Pour des informations plus détaillées sur le système DI d’Angular, reportez-vous à Documentation angulaire officielle sur l’injection de dépendance.
Source link