Les services
Introduction
Dans ce chapitre le sujet des services Angular sera abordé. Les points concernant leur déclaration et leur utilisation seront vus. Nous verrons également comment gérer les traitements asynchrones au sein d’un service, ainsi que la notion de notification de changement des données.
Qu’est-ce qu’un service ?
L’objectif d’un service est de contenir toute la logique fonctionnelle et/ou technique de l’application. Au contraire des composants qui ne doivent contenir que la logique propre à la manipulation de leurs données.
Un des exemples très souvent utilisés est d’encapsuler les interactions avec une API dans un service pour que le composant n’ait pas à se soucier des aspects techniques liés à la communication.
Un composant consommant un service doit simplement demander des objets et les récupérer. Il ne doit pas avoir à se soucier des mécanismes et transformations mis en place pour récupérer ces données.
Déclarer son service
La déclaration d’un service se fait en créant une simple classe TypeScript. Les méthodes publiques de cette classe seront alors exposées et utilisables par les autres éléments de l’application.
Si le service est lui-même dépendant d’autres services, il est nécessaire de lui ajouter le décorateur @Injectable. En effet, celui-ci va indiquer à Angular d’injecter les dépendances de ce service lors de sa résolution. Ce décorateur n’est donc pas obligatoire lorsque le service ne possède aucune dépendance, mais le mettre dans tous les cas est une bonne pratique que nous vous conseillons.
Ainsi, pour créer un service qui renvoie simplement une suite de nombres, il suffit de réaliser une classe comme ceci :
import { Injectable } from '@angular/core';
@Injectable()
export class NumberService{
getFirstFiveNumbers(): number[]{
return [0,1,2,3,4];
}
}
Il ne reste plus qu’à injecter ce service dans un composant pour commencer à s’en servir.
Utiliser son service
Il n’est pas souhaitable de créer manuellement une instance du service à l’aide du mot-clé new pour deux raisons principales.
Premièrement cela voudrait dire que les composants devraient connaître le constructeur des services qu’ils utilisent. Donc, si ceux-ci évoluent, il faudra repasser sur chaque composant utilisant ces services. De plus, cela veut dire que c’est à la charge du composant de gérer le cycle de vie du service et de récupérer ses dépendances.
Ensuite, cela signifierait qu’une nouvelle instance du service est créée à chaque appel de chaque composant utilisant ce service. Des scénarios de mise en cache des données deviennent très compliqués à mettre en place dans cette situation.
Afin de pouvoir utiliser un service, il est donc préférable de l’injecter. Cela s’effectue en deux étapes principales.
Pour commencer, il faut ajouter un paramètre privé dans le constructeur du composant. Ce paramètre doit avoir le type du service. C’est ainsi que Angular reconnaît qu’un composant dépend d’un service.
constructor(private service: Service){}
Cependant il faut encore préciser au framework où il doit rechercher ce service. Pour cela, Angular a besoin que nous lui précisions un provider. Pour le spécifier au niveau d’un composant, il suffit de l’ajouter à la métadonnée providers du décorateur @Component. Cette métadonnée se construit à l’aide d’un tableau de types à injecter.
@Component({
...,
providers: [Service]
})
Dans l’exemple ci-dessous, nous utilisons simplement un service pour remplir la propriété d’un...
Rendre son service asynchrone
La plupart du temps, un service va avoir pour rôle de récupérer des données sur un serveur. Il ne faut cependant pas bloquer le programme pendant la récupération de ces données. Pour cela, voici deux méthodes afin de rendre un service asynchrone.
1. Les promises
Les promises sont un composant de la spécification ECMAScript 6. Elles sont des "promesses" qui permettent d’exécuter du code seulement lorsqu’elles ont terminé leur travail.
Elles fonctionnement donc avec un callback qui va recevoir les informations retournées par ces promises. Celles-ci possèdent également un deuxième callback permettant la gestion des erreurs.
promise.then((result) => console.log(result),
(error) => console.log(error));
Dans cet exemple, on affiche le résultat d’une promise. Le callback de succès affiche la valeur retournée par le promise et le callback d’erreur affiche l’erreur qui s’est produite.
Les promises ne possèdent que trois états. Dans un premier temps, la promise va être dans l’état "pending", c’est-à-dire un état indiquant que la promise est en cours d’exécution. Ensuite la promise peut passer soit à l’état "fulfilled", lorsque tout s’est bien passé, soit l’état "rejected" lorsqu’une erreur est survenue pendant le traitement.
Les promises exposent des méthodes then et catch. Ces deux méthodes prennent en paramètre un callback qui sera respectivement appelé lorsque la promise passera à l’état "fulfilled" ou "rejected".
import { NumberService } from './number-service';
import...
Notifier lorsque les données changent
Grâce à RxJS, il est possible de notifier tous les éléments abonnés d’un changement de données au sein d’un service.
C’est le comportement qui pouvait être effectué avec AngularJs en utilisant le service $rootScope et sa méthode broadcast.
Pour arriver à ce résultat avec Angular, il faut utiliser la classe Subject ou une de ces dérivées. Cette classe implémente à la fois Observable (il est possible d’y souscrire) et à la fois Observer (il est donc possible d’appeler la méthode next comme dans la méthode Observable.create).
let subject = new Subject<string>();
subject.subscribe(x => console.log(x));
subject.next('hello');
subject.next('world');
Dans cet exemple, le callback passé lors de souscription va s’exécuter deux fois : une pour chaque appel de la méthode next. Le résultat de ces quelques lignes de code va donc être un affichage de "hello world" dans la console.
Il est également possible d’appeler les autres méthodes issues de la classe Observer. Par exemple il est possible de terminer l’observable en appelant la méthode complete. Les actions effectuées ensuite sur le Subject seront ignorées.
let subject = new Subject<string>();
subject.subscribe(x => console.log(x));
subject.next('hello');
subject.complete();
subject.next('world');
Le résultat de cet exemple sera donc l’affichage de "hello" dans la console. En effet, l’appel à next après la méthode complete est ignoré.
Il est également possible d’utiliser la méthode error. Cette méthode va déclencher un callback d’erreur...