Dans le chapitre précédent, vous avez créé un service qui contient tous vos FaceSnaps, et que vous utilisez dans FaceSnapListComponent.
Cependant, dans une application totalement dynamique, on peut imaginer que ces FaceSnaps viendraient d'un serveur, ou d'une autre partie de l'application, et qu'il faudrait appeler une méthode pour les récupérer. D'ailleurs, toute modification d'un FaceSnap entraînerait également un appel au serveur.
Il faudra donc centraliser toutes les interactions avec les FaceSnaps dans FaceSnapsService, et c'est exactement ce que vous allez faire maintenant !
Centralisez les interactions
La première méthode que vous allez créer est getAllFaceSnaps()
. Cette méthode retournera, comme son nom l'indique, tous les FaceSnaps contenus dans le service.
Il s'agit d'une méthode TypeScript, donc il est vivement conseillé de stipuler son type de retour – ici, il s'agit d'un tableau de FaceSnap
:
getAllFaceSnaps(): FaceSnap[] {
return this.faceSnaps;
}
Même si le comportement n'est pas modifié pour l'instant, cette structure vous permettrait à terme de changer l'implémentation de getAllFaceSnaps()
sans casser le reste de l'application.
Maintenant pour quelque chose d'un peu plus complexe : le snap ! Pour l'instant, tout se passe à l'intérieur de FaceSnapComponent. Personne à l'extérieur du component ne sait que quelque chose a changé. Une vraie implémentation de cette fonctionnalité ferait certainement un appel au backend pour augmenter le nombre de snaps
dans la base de données, donc, comme pour toutes les interactions, il faut la faire passer par le service.
Pour l'instant, vous n'avez aucun moyen d'identifier un FaceSnap directement. Ajoutez dès maintenant une propriété obligatoire id
de type number
à votre modèle FaceSnap :
export class FaceSnap {
id!: number;
title!: string;
description!: string;
imageUrl!: string;
createdDate!: Date;
snaps!: number;
location?: string;
}
Et ajoutez un id
différent à chacun des FaceSnaps dans votre service, par exemple :
{
id: 1,
title: 'Archibald',
description: 'Mon meilleur ami depuis tout petit !',
imageUrl: 'https://cdn.pixabay.com/photo/2015/05/31/16/03/teddy-bear-792273_1280.jpg',
createdDate: new Date(),
snaps: 47,
location: 'Paris'
},
Cet identifiant unique va vous permettre de snap un FaceSnap par son identifiant !
Prenez quelques instants pour réfléchir à comment vous implémenteriez une méthode pour ça.
…
…
Trouvé ? Voici une solution possible :
snapFaceSnapById(faceSnapId: number): void {
const faceSnap = this.faceSnaps.find(faceSnap => faceSnap.id === id);
if (faceSnap) {
faceSnap.snaps++;
} else {
throw new Error('FaceSnap not found!');
}
}
Cette méthode :
cherche un FaceSnap par son
id
dans le tableau faceSnaps avec la fonctionfind()
;si le FaceSnap existe, on lui incrémente ses
snaps
;sinon, on
throw
une erreur.
Pour tester cette méthode, il faudra injecter FaceSnapsService dans FaceSnapComponent. Vous vous souvenez comment faire ?
constructor(private faceSnapsService: FaceSnapsService) {}
Du coup, il faut appeler la méthode que vous venez de créer dans onSnap()
:
onSnap() {
if (this.buttonText === 'Oh Snap!') {
this.faceSnapsService.snapFaceSnapById(this.faceSnap.id);
this.buttonText = 'Oops, unSnap!';
} else {
this.faceSnap.snaps--;
this.buttonText = 'Oh Snap!';
}
}
Il ne reste plus qu'à implémenter une méthode pour "unsnap" ! On pourrait très bien faire :
unsnapFaceSnapById(faceSnapId: number): void {
const faceSnap = this.faceSnaps.find(faceSnap => faceSnap.id === id);
if (faceSnap) {
faceSnap.snaps--;
} else {
throw new Error('FaceSnap not found!');
}
}
Là, votre Spider-sense devrait être en train de crier. On vient d'écrire deux fois la même méthode avec une seule ligne qui change.
Alors comment remédier à ça ? Comment refactoriser ce code pour avoir quelque chose de propre ?
Voici ma proposition : on crée une méthode getFaceSnapById()
qui s'occupe de récupérer le FaceSnap et de throw
une erreur si on ne le trouve pas. Cela risque d'être une fonctionnalité très utile à plein d'endroits. On a deux options ensuite :
créer deux méthodes : une pour snap, une pour unsnap ;
créer une seule méthode à laquelle on passe un argument qui dit s'il s'agit d'un snap ou d'un unsnap.
Je vous propose la deuxième approche ici, non pas parce qu'elle est forcément "meilleure", mais parce que je voudrais vous montrer un coup de ninja TypeScript : les literal types.
Précisez les types avec les Literal Types
D'abord, regardons la méthode getFaceSnapById()
:
getFaceSnapById(faceSnapId: number): FaceSnap {
const faceSnap = this.faceSnaps.find(faceSnap => faceSnap.id === faceSnapId);
if (!faceSnap) {
throw new Error('FaceSnap not found!');
} else {
return faceSnap;
}
}
Cette méthode retourne un FaceSnap si elle le trouve (d'où son type de retour), et throw
une erreur sinon.
Maintenant la partie fun : vous allez modifier l'empreinte de snapFaceSnapById()
pour qu'elle accepte un deuxième argument qui permettra de choisir le snapType
– un snap, ou un unsnap.
Voici une première idée :
snapFaceSnapById(faceSnapId: number, snapType: string): void {
const faceSnap = this.getFaceSnapById(faceSnapId);
snapType === 'snap' ? faceSnap.snaps++ : faceSnap.snaps--;
}
Cette méthode utilise getFaceSnapById()
pour récupérer le FaceSnap, et si le deuxième argument est 'snap'
, rajoute un snap ; sinon, elle enlève un snap.
Cependant, on pourrait passer n'importe quelle chaîne de caractères à cette méthode. Afin de limiter les possibilités à des options sémantiques, on peut remplacer le type string
par un literal type :
snapFaceSnapById(faceSnapId: number, snapType: 'snap' | 'unsnap'): void {
Ainsi, vous ne pourrez passer que 'snap' ou 'unsnap' comme deuxième argument. Non seulement votre IDE vous préviendra si vous essayez de passer autre chose, mais l'autocomplétion et la documentation automatique faciliteront l'utilisation de cette méthode :

Avec ça, dans FaceSnapComponent, l'implémentation devient :
onSnap() {
if (this.buttonText === 'Oh Snap!') {
this.faceSnapsService.snapFaceSnapById(this.faceSnap.id, 'snap');
this.buttonText = 'Oops, unSnap!';
} else {
this.faceSnapsService.snapFaceSnapById(this.faceSnap.id, 'unsnap');
this.buttonText = 'Oh Snap!';
}
}
Et toutes les interactions avec les FaceSnap passent par le service !
En résumé
Centraliser les interactions dans un service sous forme de méthodes crée une structure plus modulaire, qui facilite la maintenance et les évolutions de votre application.
Comme dans toute base de code, refactorisez pour éviter de répéter des blocs de code (le principe DRY : Don't Repeat Yourself).
Les literal types permettent de créer rapidement des types personnalisés, souvent utilisés pour limiter les choix pour un argument de méthode, par exemple :
fileType: 'image' | 'video'
Maintenant que nous avons centralisé les interactions dans un service, nous pouvons ajouter du routing – rendez-vous au prochain chapitre !