• 10 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 13/06/2022

Centralisez votre logique avec les Services

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 fonction  find()  ;

  • 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 :

Mon IDE me spécifie qu'il faut que je passe 'snap' ou 'unsnap'
Propositions IDE

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 !

Exemple de certificat de réussite
Exemple de certificat de réussite