Retour au cours

frontend / typescript

Decorators

Leçon 111 exercice

Explication

Ce que vous allez apprendre

  • Appliquer un décorateur de classe avec la syntaxe @nomDuDecorateur
  • Écrire un décorateur qui journalise ou mesure le temps d'exécution d'une méthode
  • Distinguer les décorateurs stage-3 natifs (TypeScript 5+) de l'ancienne syntaxe expérimentale
  • Créer une factory de décorateur pour un décorateur paramétrable (ex : vérifier un rôle)
  • Reconnaître un cas où un décorateur évite de dupliquer une préoccupation transversale

Dans quel contexte ?

Une API a une vingtaine de routes protégées, chacune commençant par un bloc identique de dix lignes qui vérifie que l'utilisateur a le rôle "admin", sinon lève une erreur 403. Ce code dupliqué finit par diverger légèrement d'une route à l'autre au fil des modifications, créant des incohérences de sécurité difficiles à repérer. En extrayant cette logique dans un décorateur paramétrable @RequireRole("admin") appliqué directement sur chaque méthode de route, la vérification devient centralisée, testée une seule fois, et impossible à oublier ou à mal recopier.

Ajouter un comportement sans modifier le code existant

Un décorateur est une fonction qui s'attache à une classe, une méthode ou une propriété pour en modifier ou en enrichir le comportement, sans toucher au code original. C'est un peu comme une note collée sur une porte qui dit d'essuyer ses pieds avant d'entrer : la porte elle-même n'a pas changé, mais un comportement supplémentaire s'exécute autour de son utilisation.

Prérequis

Cette leçon suppose les classes typées (leçon 10) et les génériques (leçon 5) bien acquis : un décorateur de classe est une fonction générique qui reçoit un constructeur en argument.

Des cas d'usage concrets

Les décorateurs sont particulièrement utiles pour des préoccupations transversales qui reviennent dans beaucoup de classes différentes : journaliser chaque appel de méthode, mesurer le temps d'exécution, valider automatiquement une valeur assignée, ou vérifier un rôle avant d'autoriser une action. Plutôt que de dupliquer cette logique dans chaque méthode concernée, on l'écrit une fois sous forme de décorateur et on l'applique où nécessaire avec la syntaxe @nomDuDecorateur.

Cible du décorateurExemple d'usage
ClasseJournaliser chaque création d'instance
MéthodeMesurer le temps d'exécution, vérifier un rôle
PropriétéValider automatiquement une valeur assignée

Les décorateurs stage-3 natifs

Depuis TypeScript 5, les décorateurs suivent la proposition standard du langage JavaScript lui-même (stage 3), ce qui signifie qu'ils n'ont plus besoin d'un flag expérimental dans la configuration et se rapprochent de ce que d'autres langages orientés objet, comme Java, Python ou C#, proposent depuis longtemps sous des noms différents (annotations, décorateurs).

Piège fréquent

Les décorateurs stage-3 (TypeScript 5+, natifs) et les anciens décorateurs "experimentalDecorators" (utilisés par Angular historiquement) ont une signature différente et ne sont pas interchangeables. Vérifiez toujours quelle version votre framework attend avant d'écrire un décorateur personnalisé.

Les factories de décorateurs

Quand un décorateur a besoin d'un paramètre, comme le nom d'un rôle à vérifier, on écrit une fonction qui retourne le véritable décorateur : un décorateur paramétrable. C'est un pattern à retenir, il revient constamment dans des frameworks comme Angular ou NestJS, qui reposent massivement sur les décorateurs.

Commandes & code

Decorators

ts
// Décorateurs stage-3 (natifs, TypeScript 5+), pas besoin de flag expérimental.

// Décorateur de classe : reçoit la classe cible, peut la remplacer/étendre
function journaliser<T extends { new (...args: any[]): {} }>(cible: T) {
  return class extends cible {
    constructor(...args: any[]) {
      super(...args);
      console.log(`Instance de ${cible.name} créée`);
    }
  };
}

@journaliser
class Service {
  demarrer() {
    console.log("Service démarré");
  }
}

// Décorateur de méthode : intercepte l'appel (logging, timing, cache...)
function mesurerTemps(cibleMethode: any, context: ClassMethodDecoratorContext) {
  return function (this: any, ...args: any[]) {
    const debut = performance.now();
    const resultat = cibleMethode.apply(this, args);
    console.log(`${String(context.name)} exécutée en ${performance.now() - debut}ms`);
    return resultat;
  };
}

class Calculateur {
  @mesurerTemps
  calculerFactorielle(n: number): number {
    return n <= 1 ? 1 : n * this.calculerFactorielle(n - 1);
  }
}

// Décorateur de propriété : valide/transforme une valeur assignée
function positif(cible: undefined, context: ClassFieldDecoratorContext) {
  return function (this: unknown, valeurInitiale: number) {
    if (valeurInitiale < 0) throw new Error(`${String(context.name)} doit être positif`);
    return valeurInitiale;
  };
}

class Produit {
  @positif
  prix: number = 0;
}

// Décorateur paramétrable : une factory qui retourne le vrai décorateur
function role(nomRole: string) {
  return function (cibleMethode: any, context: ClassMethodDecoratorContext) {
    return function (this: any, ...args: any[]) {
      console.log(`Vérification du rôle "${nomRole}" avant exécution`);
      return cibleMethode.apply(this, args);
    };
  };
}

class Admin {
  @role("superadmin")
  supprimerCompte(id: number) {
    console.log(`Compte ${id} supprimé`);
  }
}

Résumé

  • Un décorateur de classe reçoit la classe et peut la remplacer par une sous-classe enrichie.
  • Un décorateur de méthode reçoit la fonction ET un context (nom, kind, static...).
  • Une factory de décorateur (role("admin")) permet de paramétrer le comportement injecté.

Exercices pratiques

1 disponible
1

Mission : centraliser la vérification de rôle avec un décorateur

Objectif : Remplacer un bloc de vérification de rôle dupliqué dans une vingtaine de routes par un décorateur paramétrable centralisé.

Contexte

Une API a une vingtaine de routes protégées, chacune commençant par un bloc identique de dix lignes qui vérifie le rôle "admin". Ce code dupliqué a fini par diverger légèrement d'une route à l'autre, créant des incohérences de sécurité. Ta mission : extraire cette logique dans un décorateur paramétrable réutilisable.

Résoudre l’exercice →