Fermer

mars 3, 2025

Injection de dépendance en angulaire

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 Calculatorplutô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:

  1. Couplage serré – Le ReceiptComponent est étroitement couplé au Calculator classe, ce qui rend difficile de remplacer ou d’étendre la fonctionnalité de la calculatrice sans modifier le composant.
  2. Manque de testabilité – tester le ReceiptComponent isolément devient difficile car nous ne pouvons pas facilement se moquer du Calculator classe. Se moquer ou le remplacer par un double test n’est pas simple car le ReceiptComponent gère directement l’instanciation.
  3. Duplication de code – si plusieurs composants nécessitent le Calculatorchacun créera sa propre instance, gaspillant les ressources et rendant la maintenance plus lourde.

Idéalement, nous serions se découpler le ReceiptComponent de Calculatorpermettant 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 au Calculator Interface, simplifiant les remplacements ou les extensions.
  • Testabilité – Le Calculator peut facilement être moqué pendant les tests, permettant le ReceiptComponent à 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, ConfigurableServicequi 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 InjectionTokenun 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