10 - Les Services
Introduction
Avoir une application découpée en plein de petits composants nous met face à un problème : parfois, 2 composants différents peuvent avoir besoin d'effectuer la même tâche. Dans cette situation, on peut :
- ❌ Copier coller le code correspondant à cette tâche dans les 2 composants (non, jamais, svp)
- ✅ Utiliser les services (oui, bien sûr, toujours 😌)
Objectifs
✅ Découvrir les services et leur utilité ✅ Créer un service ✅ Injecter et utiliser un service
Avant de démarrer, assure-toi d'avoir terminé les quêtes suivantes👇
C'est l'histoire d'un service client qui entre dans un bar...
Admettons que tu développes une application pour un hôtel. Le patron de l'enseigne vient te trouver avec le besoin suivant : créer une application permettant à ses clients de commander des cocktails depuis leur smartphone. L'application en question doit pouvoir proposer un large choix de cocktails.
Sur ce type d'application, il y a souvent plusieurs écrans (composants) qui permettent d'accéder à la liste des cocktails (l'écran d'utilisateur, l'écran d'administration, les favoris, etc...).
Il paraît donc intelligent de mutualiser ces morceaux de code (pour récupérer les cocktails) à un seul et même endroit : un service.
✅ Le rôle des composants est de faire le lien entre les données issues du service et le template (html) associé au composant (que l'on appelle aussi la vue).
Mais Angular ajoute une notion supplémentaire, la notion d'injection de dépendance.
L'injection de dépendance est une notion fondamentale en programmation orientée objet, tu en entendras donc beaucoup parler dans tes années futures. Pas besoin ici de creuser trop loin, mais en quelques mots : Les services ne sont pas créés par les composants qui y font appel. Ce rôle revient à un bout de code, une unité logicielle qui regroupe un lot de fonctionnalités comprises au sein même du framework et que l'on appelle l'injecteur de dépendances.
Le rôle de l'injecteur de dépendances est de créer à notre place les objets correspondant à nos services et de les envoyer aux composants qui en ont l'utilité. Cela nous permet d'écrire du code faiblement couplé, ce qui augmente considérablement la maintenance et la souplesse de nos applications.
👀 À petite échelle, la différence est peu visible. Mais demain tu seras sans doute amené·e à travailler sur de grosses applications : c'est là où le couplage faible, la facilité de maintenance et la souplesse du code deviennent absolument fondamentaux pour un projet pérenne 🤓
Pour en apprendre un peu plus sur le fonctionnement de l'injection de dépendances d'Angular.
Mais ça ressemble à quoi un service ?
Voici comment se présente un service dans Angular (ici, admettons que nous ayons créé un service hello.world.service.ts) :
ng generate service hello-world
Ce qui donne :
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class HelloWorldService {
constructor() { }
getHelloWorld(): string {
return "Hello world";
}
}
Le service est assez simple. Il possède une unique méthode getHelloWorld() qui renvoie une chaîne de caractères "Hello world".
Le décorateur @Injectable() précise que les objets de type HelloWorldService peuvent être créés par Angular et injectés au sein des composants qui en ont besoin.
Admettons que notre composant AppComponent ait besoin de la méthode getHelloWorld() écrite dans notre service. Nous devons alors injecter notre service au sein de app.component.ts :
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HelloWorldService } from './hello-world.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: ['./app.component.css']
})
export class AppComponent {
constructor(private helloWorldService: HelloWorldService) { }
onClick() {
console.log(this.helloWorldService.getHelloWorld());
}
}
Pour se faire injecter un service de type HelloWorldService, on ajoute un paramètre de type HelloWorldService au constructeur de notre classe AppComponent. Ce paramètre est en visibilité private.
Il existe une autre manière, plus récente, d'injecter notre service. Pour cela, on utilise la méthode inject():
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HelloWorldService } from './hello-world.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: ['./app.component.css']
})
export class AppComponent {
private helloWorldService = inject(HelloWorldService);
onClick() {
console.log(this.helloWorldService.getHelloWorld());
}
}
La méthode inject() a été introduite avec la version 14 d'Angular, qui est sortie en juin 2022. Cette méthode permet de rendre le code plus concis en offrant une alternative à l'injection classique basée sur le constructeur.
Challenge 💪
Créer un service "CocktailService" 🍸
Créer un service CocktailService permettant de retourner un certain nombre d'objets de type Cocktail.
Un objet de type Cocktail doit posséder les caractéristiques suivantes :
- Un champ nom
- Un champ prix
- Un champ image
Le service contient une méthode getCocktails() retournant l'ensemble des cocktails disponibles.
La liste des cocktails s'affiche dans un composant nommé CocktailListComponent.
Profitons de cette quête pour également te sensibiliser aux tests unitaires :
→ Créer un fichier nommé test.spec.ts, place-le dans le dossier app/ et colle à l'intérieur le code suivant :
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { CocktailListComponent } from './cocktail-list/cocktail-list.component';
import { CocktailService } from './cocktail.service';
import { HttpClientModule } from '@angular/common/http';
describe('CocktailListComponent', () => {
let component: CocktailListComponent;
let fixture: ComponentFixture<CocktailListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HttpClientModule, CocktailListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CocktailListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('service should be created', () => {
const service: CocktailService = TestBed.inject(CocktailService);
expect(service).toBeTruthy();
});
it('service should have a "getCocktails" method which returns at least one object', () => {
const service: CocktailService = TestBed.inject(CocktailService);
const cocktails = service.getCocktails()
expect(cocktails.length).toBeGreaterThan(0);
});
it('should create a CocktailListComponent instance', waitForAsync(() => {
const fixture = TestBed.createComponent(CocktailListComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(
'component should have a public property named "cocktails"',
waitForAsync(
() => {
const fixture = TestBed.createComponent(CocktailListComponent);
fixture.detectChanges();
expect(fixture.componentInstance.cocktails).toBeTruthy();
}
)
);
});
Ne prends pas peur et lis le code ci-dessus : tu y trouveras pas mal d'informations pour réussir la quête 🤓
Poste ensuite ton résultat via un repository github.
Critères de validation ✅
- Le service contient une méthode
getCocktails()retournant l'ensemble des cocktails - Le composant doit posséder une propriété nommée
cocktails - Le service
CocktailServiceest injecté au sein du composant. - Le composant utilise la méthode
getCocktails()du service et récupère les cocktails - L'ensemble des cocktails est affiché via la directive ngFor au sein du template associé au composant
- Le code de ton composant doit se trouver dans le dossier app/cocktail-list/
- Le code de ton service doit se trouver dans le dossier app/
- Le code doit passer les tests unitaires. Pour cela utilise la commande
ng test
Ressources partagées sur cette quête
- bon récapitulatif simplehttps://www.youtube.com/watch?v=HJueQrPaFec