Prenez votre courage à deux mains !
Dans une application complète Angular, vous aurez des dizaines, voire des centaines de components différents. Ces components auront très souvent besoin d'accéder aux mêmes données. Il y aura des interactions courantes avec ces données, comme l'écriture, la modification et la suppression. L'application peut également avoir besoin d'interagir avec un serveur concernant ces données, pour la lecture et l'enregistrement.
Pour mieux organiser votre code, pour éviter de répéter des blocs et pour toujours avoir des données à jour partout dans l'application, ce serait intéressant de pouvoir regrouper les données et leurs méthodes ensemble, de les centraliser. En Angular, cette centralisation se fait dans des services.
Juste avant de vous lancer dans la création de votre premier service, il faudra restructurer légèrement votre application.
Restructurez votre application
Pour l'instant, AppComponent s'occupe de générer les FaceSnapComponents. Mais ce n'est pas un bon choix, surtout si vous voulez créer une structure plus complexe par la suite. Vous allez créer un nouveau component, FaceSnapListComponent, qui sera le parent de tous vos FaceSnapComponents.
Générez le nouveau component avec la commande :
ng generate component face-snap-list
Ce nouveau component sera le parent des FaceSnapComponent : il a donc besoin du tableau qui se trouve actuellement dans AppComponent. Il faut aussi lui transplanter le template d'AppComponent.
face-snap-list.component.ts
doit contenir :
export class FaceSnapListComponent implements OnInit {
faceSnaps!: FaceSnap[];
ngOnInit() {
this.faceSnaps = [
//...
];
}
}
Et face-snap-list.component.html
:
@for (faceSnap of faceSnaps; track faceSnap.title) {
<app-face-snap [faceSnap]="faceSnap" />
}
La dernière étape de cette refactorisation est d'ajouter FaceSnapListComponent au template de AppComponent :
<app-face-snap-list />
Visuellement, votre application n'a pas changé, mais sa structure a été améliorée :
la logique qui gère la liste des FaceSnaps se trouve maintenant dans un component prévu pour ;
et AppComponent ne contient que ce qu'il faut pour afficher les éléments principaux de l'application.
Ajoutez tout de suite un nouvel élément principal, HeaderComponent :
ng g c header
Pour afficher le header au-dessus de la liste de FaceSnap, il suffit de l'ajouter dans le template de AppComponent :
<app-header />
<app-face-snap-list />
Je vous propose un header très simple pour l'instant :
Son template :
<header>
<h1>snapface</h1>
</header>
Et des styles simples :
header {
text-align: center;
background-color: #EEE;
margin-bottom: 20px;
padding: 10px;
}
La structure est prête : il est maintenant temps de créer un service pour gérer les FaceSnaps.
Partagez les données
Vous allez maintenant créer un service qui va centraliser toutes les interactions avec les FaceSnaps, afin que tous les éventuels components de votre application dépendent des mêmes données.
Dans le dossier app
, créez un dossier services
et dedans un fichier face-snaps.service.ts
.
Un service est une classe, et la façon la plus simple de déclarer une classe comme étant un service est d'utiliser le décorateur @Injectable()
qui s'importe depuis @angular/core
:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class FaceSnapsService {
}
Le premier élément que vous allez déporter dans votre service est le tableau des FaceSnaps. Il est important de savoir qu'un service n'a pas de méthode ngOnInit()
, car les services ne sont pas instanciés de la même manière que les components. Il faudra donc déclarer et initialiser le tableau dans la même expression, et ajouter une méthode pour les récupérer :
import { Injectable } from '@angular/core';
import { FaceSnap } from './models/face-snap.model';
@Injectable({
providedIn: 'root'
})
export class FaceSnapsService {
private faceSnaps: FaceSnap[] = [
// vos FaceSnap ici
];
getFaceSnaps(): FaceSnap[] {
return [...this.faceSnaps];
}
}
Tout ça est très bien, mais comment on fait pour les récupérer ?
Voyons ça de suite !
Injectez votre service
Pour pouvoir utiliser un service dans un component, il faut utiliser le système d'injection de dépendances (dependency injection ou DI) que vous fournit Angular. C'est très simple : vous passez un argument du type du service au constructor du component, et Angular vous mettra à disposition la bonne instance du service. Concrètement pour votre application, dans FaceSnapListComponent :
import { FaceSnapsService } from '../services/face-snaps.service';
//...
constructor(private faceSnapsService: FaceSnapsService) { }
Pour rappel, ajouter un modificateur d'accès comme public
ou private
à un argument du constructor
crée une propriété avec ce nom-là dans la classe. Vous aurez donc accès au service via la propriété faceSnapsService
.
Maintenant que vous avez accès au service, vous pouvez modifier l'initialisation de la propriété faceSnaps
du component pour y attribuer le tableau du service :
ngOnInit(): void {
this.faceSnaps = this.faceSnapsService.getFaceSnaps();
this.faceSnaps[1].setLocation('à la montagne');
}
Et votre application refonctionne, sauf que maintenant elle utilise les données du service !
Pour que tous les components qui pourraient vouloir récupérer les FaceSnaps reçoivent le FaceSnap avec sa localisation, je vous propose d'ajouter une méthode à la classe FaceSnap
:
export class FaceSnap {
// ...
withLocation(location: string): FaceSnap {
this.setLocation(location);
return this;
}
}
Pourquoi return this
? Quel est l'avantage de cette approche ?
Et bien, dans le service, cela nous permet d'ajouter la localisation au FaceSnap sans avoir besoin d'un contexte où l'on pourrait appeler setLocation
:
// ...
export class FaceSnapsService {
private faceSnaps: FaceSnap[] = [
// ...
new FaceSnap(
'Three Rock Mountain',
'Un endroit magnifique pour les randonnées.',
'https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Three_Rock_Mountain_Southern_Tor.jpg/2880px-Three_Rock_Mountain_Southern_Tor.jpg',
new Date(),
6
).withLocation('à la montagne'),
// ...
];
// ...
}
Cette approche permet de configurer un objet lors de son instantiation, et d'une manière très lisible ! Notre tableau contient donc un FaceSnap avec sa localisation.
En résumé
Les services permettent de centraliser les données et la logique pour les différents domaines de votre application.
Créer un service est aussi simple qu'ajouter le décorateur
@Injectable()
à une classe.Pour injecter un service dans un component, ajoutez un argument au constructor du component qui a le type du service, par exemple
private userService: UserService
Allons plus loin dans la centralisation de la logique de FaceSnap dans le chapitre suivant !