Apprendre Angular en vidéos, ça te tente?
Comment gérer l’injection de dépendances dans Angular?

Comment gérer l’injection de dépendances dans Angular?

Nous nous retrouvons dans cet article pour apprendre à gérer les injections de dépendances dans vos projets Angular.

Les injections de dépendances ont toujours été un point très important dans les projets Angular. Elles permettaient d’inclure divers composants au sein de vos applications. Bon honnêtement, on ne savait pas trop comment ces dépendances étaient créées, ni si on en avait besoin ou pas.

Au long de cet article, nous allons revenir rapidement sur le système d’injection de dépendances d’AngularJS (Angular 1.x). Nous allons voir comment le système propre à Angular (Angular 2+) va nous permettre de palier aux diverses problèmes que l’on rencontrait dans AngularJS.

Ce n’est pas tout. Une nouvelle notion rentre en compte à partir de la version 5.0.0, nous la verrons également.

Avant de rentrer dans le vif du sujet, je pense qu’une petite piqûre de rappel sur les injections de dépendances est la bienvenue.

L’injection de dépendances permet d’appeler les dépendances en utilisant des providers. Ils vont uniquement appeler les dépendances dont vous aurez besoin lors de l’exécution. On gagne ainsi en performance car on appelle uniquement les dépendances dont nous avons besoin (dans notre cas, des composants, des services, etc…). Le design pattern qui lui est associé s’appelle l’inversion de contrôle.

 

L’approche framework et ses problèmes

L’approche framework nous oblige à revenir à AngularJS (Angular 1.x). AngularJS a son propre système d’injection de dépendances. Il permet d’annoter des services ou des composants. Ensuite l’injector va trouver ces services et ces composants, et sait par conséquent quelles dépendances dont il a besoin.

Nous allons voir un exemple d’une classe dans AngularJs qui est annotée par trois autres classes :

class Desert{

}

Desert.$inject = ['Pyramide','Sable','Scorpion'];

Ensuite nous définissons notre classe Desert comme un service.


var app = angular.module('masuperApp',[]);

app.service('Desert',Desert);

app.service('AutreService', function(Desert){

//Vous pouvez appeler votre instance de Desert

});

Tout ça est super, mais cache plusieurs problèmes :

  • Les dépendances sont utilisées comme des singletons. Quand nous appelons un service, il est créé seulement une fois pour notre lifecycle d’application. Il en résulte des problèmes de cache interne
  • Nous pouvons avoir des collisions au niveau des noms. Dans notre cas, si nous voulons utiliser une autre partie de l’application qui se sert aussi d’un service nommé Desert, nous allons avoir un problème de nom.
  • L’injection de dépendances se build à l’intérieur du framework. Si nous voulons avoir un système d’injection de dépendance indépendant, cela n’est pas possible.

Nous allons voir comment ces problèmes sont traités dans Angular.

 

Injections de dépendances dans Angular

Attention, si vous utilisez Angular 2 ou 4, l’injection de dépendance se fait par ReflectiveInjector API. Si vous utilisez Angular 5, une nouvelle API est disponible : StaticInjector.

Les concepts restent exactement les mêmes. Je vais présenter l’API la plus utilisée actuellement soit ReflectiveInjector. J’exposerai un peu plus loin dans cet article un exemple d’utilisation avec StaticInjector.

Essayons dans un premier temps de comprendre le concept avant de se lancer à l’assaut du code :

 

 

Une petite explication s’impose. L’injection de dépendances en Angular est consistée de trois choses :

  • L’objet Injector permet de créer des instances de la dépendance
  • Le Provider dit à l’injector comment créer une instance de telle ou telle dépendance.
  • Un objet est créé et la dépendance permet de dire quel est le type de l’objet

Cela reste très théorique mais nous avons une meilleure idée générale du concept au sein d’Angular.

Nous allons garder notre classe Desert pour l’exemple qui suit :

(si vous voulez directement un exemple avec la nouvelle API StaticInjector (Angular 5), aller directement à la section concernée)


import { ReflectiveInjector } from '@angular/core'

var injector = ReflectiveInjector.resolveAndCreate([
    Desert,
    Pyramide,
    Sable,
    Scorpion
]);
var desert = injector.get(Desert);

 

Nous commençons par importer logiquement ReflectiveInjector depuis Angular. ReflectiveInjector va nous servir à exposer l’API d’Angular pour l’injection de dépendances. Il va nous permettre de créer des injectors. La fonction (factory) resolveAndCreate va rendre possible la création d’un injector. Nous devons lui fournir une liste de providers. la méthode injector.get(), va nous aider à créer une instance de Desert. Nous devons cependant dire à notre classe Desert de quoi il va avoir besoin au niveau de ses dépendances.

Nous allons voir cela maintenant :


import { Inject } from '@angular/core';

class Desert {
  constructor(
    @Inject(Pyramide) pyramide,
    @Inject(Sable) sable,
    @Inject(Scorpion) scorpion
  ) {
    ...
  }
}

Pour préciser les dépendances de notre classe Desert, nous devons utiliser le décorateur Inject. Il est importé directement depuis le framework Angular (@angular/code).

Il faut utiliser le décorateur Inject pour chaque dépendance, et les mettre dans le constructor. Pendant l’exécution de votre application, le système d’injection de dépendances va les consommé et va créer des instances de type Pyramide, Sable et Scorpion.

Ce code est écrit avec la norme ES5, je vous l’accorde c’est un peu barbare. Le but étant de bien comprendre le fonctionnement d’injection de dépendances avec le framework Angular.

Normalement, vous devez développer en TypeScript, ce qui donnerait quelque chose comme cela :


class Desert {
  constructor(pyramide: Pyramide, sable: Sable, scorpion: Scorpion) {
    ...
  }
}

 

Parfait, à present notre classe est capable d’instancier ses propres dépendances. Lors de la création d’un objet Desert, il est possible de connaître les informations que nous avons besoin pour cette classe.

C’est là où notre méthode resolveAndCreate rentre en jeu. Vous vous en souvenez :


var injector = ReflectiveInjector.resolveAndCreate([
  Desert,
  Pyramide,
  Sable,
  Scorpion
]);

 

Ci-dessus, nous avons la syntaxe courte pour déclarer des providers. Voyons une version un peu plus longue :


var injector = RelfectiveInjector.resolveAndCreate([
  { provide: Desert, useClass: Desert },
  { provide: Pyramide, useClass: Pyramide },
  { provide: Sable, useClass: Sable },
  { provide: Scorpion, useClass: Scorpion }
]);

Ce code est beaucoup plus compréhensible, vous ne trouvez pas?

Pour chaque provider, il est possible d’indiquer la classe correspondante. Quand on instancie un objet avec le type Desert, on utilise la classe Desert. L’attribut provide permet de définir le provider que l’on va appeler pour instancier un objet, et useClass va nous aider à mapper la classe que l’on compte utiliser.

Alors là, on se demande à quoi ça peut bien servir d’utiliser cette syntaxe plutôt que la syntaxe courte.

Dans ce cas, il n’y a aucune raison de changer la classe Desert de son provider Desert. C’est vrai mais regardons un autre exemple :

  { provide: Desert, useClass: MockDesert },

Nous pouvons mapper un provider avec n’importe quelle classe. Ainsi ,nous mappons Desert avec la classe MockDesert. Il y a plusieurs avantages à ceci :

  • Prévenir les conflits de noms
  • Changer une dépendance sans toucher son code
  • Utiliser des mocks pour les tests unitaires

Nous allons voir maintenant les différents couples de providers introduits par Angular.

Autres configurations de providers

La plupart du temps, nous voulons instancier des classes. Il se peut que parfois, nous voulions instancier des factories ou des simples valeurs. Le système d’injection de dépendances d’Angular a prévu ces cas :

Une simple valeur

Il est possible de provide des simples valeurs avec useValue: value

{ provide: 'some value', useValue: 'Hello World' }

Une factory

Il est aussi possible de provide des factories :

{ 
  provide: Pyramide,
  useFactory: (IS_Kheops) => {
    if (IS_Kheops) {
      return new PyramideKheops();
    } else {
      return new PyramideGizeh();
    }
  }
}

Et comment je gère les dépendances de ma factory dans tout ça?

J’allais y venir, avec le mot clef deps : 

{
  provide: Pyramide,
  useFactory: (desert, pyramide) => {

  },
  deps: [Desert, Pyramide]
}

Nous allons voir maintenant les nouveautés dans Angular 5.

Injection de dépendances dans Angular 5.0.0

ReflectiveInjector est déprécié depuis Angular 5.0.0. Il a été remplacé par StaticInjector. Ne prenez pas peur, comme je l’ai mentionné dans l’introduction les concepts n’ont pas changé.

Nous allons voir ensemble les petites différences que cela apporte :

  • La création d’injectors se fait par Injector.create()
  • Les dépendances doivent être explicitement listées avec la propriété deps

Vous l’aurez compris la méthode resolveAndCreate() a été remplacée par create(). De plus, nous devons importer Injector à la place de ReflectiveInjector.

import { Injector } from '@angular/core';

let injector = Injector.create([
  { provide: Desert, deps: [Pyramide, Sable, Scorpion] },
  { provide: Pyramide, deps: [] },
  { provide: Sable, deps: [] },
  { provide: Scorpion, deps: [] }
]);

let desert = injector.get(Desert);

Il est aussi possible de définir les dépendances dans @NgModule() ou @Component().

Comment l’utiliser concrètement?

C’est bien, maintenant que nous avons fait le tour de quasiment toute la partie théorique, nous allons voir comment l’utiliser.

Prenons un composant Angular des plus simples (un bon vieux HelloWorld) :

@Component({
  selector: 'app',
  template: 'Hello !' 
})
class App {
  name = 'World';
}

Je n’ai pas grand chose à ajouter ici, si vous ne savez pas comment créer un composant, vous pouvez aller voir ce tutoriel

Nous voulons étendre notre composant. Nous allons créer un service NameService qui va nous renvoyer le nom que nous souhaitons afficher dans notre composant.

Le service devrait ressembler à quelque chose du genre :

class NameService {
  name = 'Paul';;

  getName() {
    return this.name;
  }
}

Comme pour la création de composant, normalement vous devez maîtriser la création de service, je vous fais confiance :). Sinon vous pouvez aller jeter un coup d’œil à ce tutoriel.

Bon, nous voulons à présent utiliser ce service dans notre composant. Voyons voir comment faire.

Normalement vous avez bootstraper votre application en définissant un NgModule. Le décorateur @NgModule sert également à renseigner les providers que vous voulez utiliser :

@NgModule({
  imports: [BrowserModule],
  providers: [NameService],
  declarations: [App],
  bootstrap: [App]
})
export class AppModule {}

Dans le constructeur de votre composant App, il faut ajouter le NameService, comme ceci:

class App {
  constructor(NameService: NameService) {
    this.name = NameService.getName();
  }
}

A noter que je vous ai donné la version “TypeScript”. Dans le cas où vous ne l’utilisez pas, vous devez vous servir du décorateur @Inject :

class App {
  constructor(@Inject(NameService) NameService) {
    this.name = NameService.getName();
  }
}

Et tout le reste se fait automatiquement. Nous allons pousser l’injection de dépendances un peu plus loin, si vous le voulez bien.

Comment fait-on pour utiliser un service pour un composant spécifique?

Il est bien tendu possible de le faire. Vous devez injecter votre service au niveau du décorateur @Component par le biais de la propriété providers. Voyons un exemple pour notre composant App et notre service NameService

@Component({
  selector: 'app',
  providers: [
    { provide: NameService, useValue: 'Thomas' }
  ],
  template: 'Hello {{name}}!'
}) 
class App {
 ... 
}

Nous en avons fini avec cet exemple concret, assez simple non?

Conclusion

Dans le framework Angular (2,4,5), ils ont mis en place un système d’injection de dépendances beaucoup plus puissant quand dans AngularJS (1.x). Ce nouveau système permet de régler les diverses problèmes que nous avons évoqués dans la première partie de cet article. Par ce biais, il est possible de faire des composants indépendants au niveau de leurs dépendances, ce qui permet de les réutiliser partout très facilement.

La version 5.0.0 a apporté encore son lot de modifications, notamment par un perfectionnement de l’API.

En espérant que cet article vous aura plu et vous aura permis d’y voir un peu plus clair dans le système d’injection de dépendances.

N’oubliez pas de partager cet article, en attendant codez bien.

Partagez, si vous aimez

2 thoughts on “Comment gérer l’injection de dépendances dans Angular?

  1. Merci clair.
    Une question sur l’exemple :
    useFactory: () => {
    if (IS_Kheops) {
    return new PyramideKheops();
    } else {
    return new PyramideGizeh();
    }
    }

    Comment je passe ce “paramètre” IS_Kheops ?

    Merci

    1. Merci pour ton commentaire, un petit oubli de ma part effectivement, il faut rajouter le paramètre dans la factory.
      useFactory: (IS_Kheops) => {
      if (IS_Kheops) {
      return new PyramideKheops();
      } else {
      return new PyramideGizeh();
      }
      }

Laisser un commentaire

Specify Facebook App ID and Secret in Super Socializer > Social Login section in admin panel for Facebook Login to work

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *