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
etClassB
, nous devons opérer des changements au niveau de la classeClassC
.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 classesClassA
etClassB
dans le constructeur de la classeClassC
.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'
etnull
, qui suivant le cas, nous amène à utiliser le tableauproviders
.
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