L'injection de dépendances en Angular: que faut t-il retenir ?

L'une des qualités du framework Angular réside en sa facilité à permettre l'utilisation de la notion d'injection de dépendances (Dependency Injection en anglais). En effet, tout le framework repose sur cette notion ce qui nous permet d'interagir avec une multitude de classes sans se casser la tête. Avant, d'aller plus loin, voyons brièvement ensemble ce qu'est l'injection de dépendances.

En programmation orientée objet, nous manipulons des objets au moyen des classes. Supposons que nous avons trois (03) classes à savoir ClassA, ClassB et ClassC comme l'illustre cet exemple de code basé sur du TypeScript :

export class ClassA {
  constructor() {
  }
}

export class ClassB {
  constructor() {
  }
}

export class ClassC {
  constructor() {
  }
}

Notre objectif ici est de créer deux (02) objets respectivement des classes ClassA et ClassB que nous utiliserons dans notre classe ClassC. Nous avons donc le code qui suit :

export class ClassC {
  objectA = new ClassA();
  objectB = new ClassB();
  constructor() {   
  }
}

Jusque-là, tout se passe bien. Supposons à présent que nos classes ClassA et ClassB contiennent des arguments dans leurs constructeurs respectifs comme l'indique le code suivant :

export class ClassA {
  constructor(args) {
  }
}

export class ClassB {
  constructor(args) {
  }
}

Ce changement au niveau de nos classes ClassA et ClassB nous contraint à modifier la façon dont nous créons nos objets dans la classe ClassC. Nous devons passer en paramètre des variables comme ceci :

export class ClassC {
  objectA;
  objectB;
  constructor(argsA, argsB) {   
   this.objectA = new ClassA(argsA);
   this.objectB = new ClassB(argsB);
  }
}

Cette façon classique de faire soulève deux (02) problèmes essentiels :

  • Le premier problème c'est que le code n'est plus flexible parce que à chaque changement au niveau du constructeur de nos classes ClassA et ClassB, nous devons opérer des changements au niveau de la classe ClassC.

  • Le second problème c'est que notre code ne sera plus vraiment facile à tester parce que à chaque fois que nous créerons une instance de la classe ClassC, nous aurons besoin d'indiquer les paramètres des classes ClassA et ClassB dans le constructeur de la classe ClassC.

Afin d'y remédier, l'astuce la plus simple est d'utiliser la notion d'injection de dépendances.

En reprenant l'exemple de nos trois (03) précédentes classes, nous avons ceci :

export class ClassA {
  constructor(args) {
  }
}

export class ClassB {
  constructor(args) {
  }
}

export class ClassC {
  objectA;
  objectB;
  constructor(private objectA: ClassA, private objectB: ClassB) {   
    this.objectA = objectA;
    this.objectB = objectB;
  }
}

Nous remarquons donc que les changements au niveau du constructeur de nos classes ClassA et ClassB ne nous obligent plus à modifier nos instances dans la classe ClassC.

Oui c'est magique 😂

En revenant sur notre thématique, Angular est entièrement basé sur le langage orienté objet TypeScript ce qui lui de mettre en place la notion d'injection de dépendances. Nous verrons quelques cas usuels d'utilisation de l'injection des dépendances dans nos projets :

  • Utilisation des classes Router, ActivatedRoute et HttpClient :

Pour créer des instances de ces classes après avoir bien-sûr déclaré leurs modules là où il le faut, voici ce que nous faisons généralement :

export class MyComponent {
    constructor(private router: Router, private route: ActivatedRoute) {
  }
}

export class MyService() {
    constructor(private http: HttpClient) {
  }
}

Résultat, nous ne nous soucions pas de comment ces instances sont initialisées. C'est de l'injection de dépendances.

  • Création d'un service :

Lorsque nous créons nos propres services, voici comment nous les utilisons dans nos composants :

export class MyComponent {
    constructor(private myService: MyService) {
  }
}

En faisant ainsi, nous exploitons la notion d'injection de dépendances. En effet, si nous remarquons bien lors de la création de nos services, nous avons le décorateur @Injectable() qui nous permet d'injecter notre service là où nous désirons l'utiliser. Généralement, nous avons ceci :

@Injectable({ provideIn: 'root' })
export class MyService() {
    constructor() {
  }
}
  • provideIn permet de préciser la portée de notre service. Lorsque nous indiquons 'root' le service est créé et disponible au lancement de l'application. Nous avons aussi d'autres valeurs à savoir 'platform', 'any' et null, qui suivant le cas, nous amène à utiliser le tableau providers.

Pour finir, nous pouvons également faire de l'injection de dépendance en utilisant la fonciton inject qui a subi des mises à jour depuis Angular 14:

/* au lieu de faire ceci : */
export class MyService() {
    constructor(private http: HttpClient) {
  }
}


/* nous pouvons faire : */
export class MyService() {
   http = inject(HttpClient);
}

En somme, nous avons à présent une idée de comment Angular raisonne par rapport à l'injection de dépendance et nous comprenons davantage pourquoi nous sommes autorisés à utiliser certaines classes sans le mot clé new .

Pour plus d'informations :

👉 Understanding Dependency Injection

👉 Dependency Injection Overview in Angular

👉 Creating Injectable Service

👉 Inject Function

Did you find this article valuable?

Support Ezéchiel Amen AGBLA by becoming a sponsor. Any amount is appreciated!