Partie 1 : Démarrer un projet sur de bonnes bases
Partie 2 : Construire un espace membre sécurisé avec JWT
Partie 3 : Implémenter des fonctionnalités métiers
Partie 4 : Bonus

5.1. L’entité User

Commençons par modéliser notre entité la plus importante, notre utilisateur. Je commence par vous donner le code source de ce fichier, et je vous explique tout juste après. Dans le fichier user.ts, ajoutez le code suivant :

export class User {
 readonly id: string; // id de l’utilisateur
 email: string; // email de l’utilisateur
 name: string; // nom de l’utilisateur
 avatar: string; // url vers la photo de profil de l’utilisateur
 pomodoroDuration: number; // durée des pomodoros
 
 constructor(options: {
  id?: string,
  email?: string,
  name?: string,
  avatar?: string,
  pomodoroDuration?: number,
 } = {}) {
  this.id = options.id || null;
  this.email = options.email || '';
  this.name= options.name || '';
  this.avatar = options.avatar || '';
  this.pomodoroDuration = options.pomodoroDuration || 1500;
 }

 get roles(): string[] {
  return this.email.endsWith('google.com') ? ['USER', 'EMPLOYEE'] : ['USER'];
 }
}

Il s’agit d’une classe assez simple, et pourtant j’imagine que vous avez déjà beaucoup de questions sur ce code. Je vous propose d’aborder d’abord les propriétés de cet objet, puis le constructeur.

Les propriétés de l’entité User

On déclare les propriétés de l’entité User de la ligne 2 à la ligne 6 Il s’agit simplement d’une succession de nom de propriété avec un type associé. Il y a cependant plusieurs particularités :

  • A la ligne 2, on utilise le mot-clé readonly. C’est un mot-clé TypeScript que vous pouvez appliquer sur une propriété d’un objet, pour vous assurer que cette propriété est en lecture seule et qu’elle ne peut pas être modifiée ailleurs dans votre application. Nous appliquons cette vérification sur l’identifiant de notre utilisateur, car l’identifiant doit être unique, et son unicité est garantie par notre base de données (que ce soit le Firestore dans notre cas, ou une autre base de données). Nous nous empêchons donc d’assigner un identifiant à un objet depuis notre application Angular :
user.id = ‘12345’; // Typescript ne vas pas être content !

Simple précaution, mais qui peut nous évider des erreurs d’inattention par la suite.

  • A la ligne 22, on définit un getter pour notre entité. Si l’utilisateur a une adresse email se terminant par “google.com”, alors on lui attribue le rôle « USER » et « EMPLOYEE ». Sinon, on lui assigne seulement les droits d’un utilisateur classique. On attribue comme type de retour string[] à la méthode roles. Cela permet d’indiquer à TypeScript que les rôles sont représentés par un tableau de chaînes de caractères.

J’ai une autre question… pourquoi la propriété ‘id’ est de type string ?

En fait, cela dépend de votre base de données. Dans le cas d’une base de données MySQL traditionnelle, les identifiants des données stockées sont des nombres. Dans le cas du Firestore, les identifiants uniques attribués aux objets sont générés sous forme de chaîne de caractères. C’est pour cela que l’on attribue le type string à nos identifiants.

Maintenant, passons à la partie qui doit le plus vous interroger, il s’agit du constructeur de notre classe.

Le constructeur de l’entité User

On déclare le constructeur de notre classe User de la ligne 12 à 33. Cela peut vous sembler beaucoup de code pour un constructeur, qui a seulement pour rôle d’initialiser les propriétés de notre classe.

En fait, la partie “spéciale” est le paramètre options du constructeur. Imaginons que je n’ajoute pas de typage à ce paramètre, le constructeur ressemblerai à ceci :

constructor(options = {})

Le constructeur est en fait assez simple, on peut passer un paramètre facultatif, qui a comme valeur par défaut un objet vide. Mais ce qu’on voudrait maintenant, c’est demander à TypeScript de vérifier que l’on passe uniquement des valeurs attendues à notre constructeur. Par exemple, que ferions-nous avec le code suivant :

let user = new User(4); 

Pas grand-chose… Le chiffre passé en paramètre est censé correspondre à quelle propriété de mon objet ? A la place, nous voudrions mettre en place un système qui nous permet de vérifier que seules des variables avec des noms correspondant aux propriétés de mon objet puissent être passé en paramètre du constructeur. Pour cela, on ajoute simplement un typage au paramètre options :

options: {
  id?: string,
  email?: string,
  name?: string,
  avatar?: string,
  pomodoroDuration?: number
}

Ainsi, nous indiquons à TypeScript que dans notre constructeur nous voulons un objet contenant une liste de propriétés facultatives (grâce à l’opérateur “?”) avec des noms identiques aux propriétés de mon objet. Si une valeur n’est pas définie en paramètre du constructeur, alors c’est la valeur par défaut qui est attribué, grâce à l’opérateur OU :

// La valeur null est la valeur par défaut de la propriété “id" :  
this.id = options.id || null; 

// Identique pour la propriété “Email” :
this.email = options.email || ‘’;

// etc...

Le typage du paramètre du constructeur, couplé avec les valeurs par défaut, nous permet une plus grande flexibilité pour instancier nos utilisateurs. Par exemple, plus tard dans le développement de notre application, on pourra faire quelque chose comme ça :

// Créer un utilisateur avec un email personnalisé,
// et les autres propriétés avec une valeur par défaut :
let customEmailUser = new User({ email: ‘john.doe@awesome.com’ });
console.info(customEmailUser.email); // affiche ‘john.doe@awesome.com’

// Créer un utilisateur avec uniquement des valeurs par défaut :
let defaultUser = New User();
console.info(defaultUser.email); // affiche ‘’.

Maintenant, nous avons en place une solution robuste pour instancier les utilisateurs de notre application.

J’espère ne pas vous avoir trop ennuyé avec toutes ces explications, mais d’après moi il était nécessaire de ne pas laisser de mystères s’installer dès le début du projet. Je vous rassure, les modèles fonctionnent tous de la même manière.