
Ca y est TypeScript 5 est sorti. Et avec cette version lâarrivĂ©e du support des âvraisâ decorators ! (pour anticiper la possible validation par la TC39 en 2024).
Voyons ça de suite avec la crĂ©ation de deux decorateurs : un premier pour se chauffer : un logger de mĂ©thode, puis nous passerons Ă la construction dâun dĂ©corateur de validation.
Logger de méthode
Imaginons une classe comme suit :
import { logged } from './decorators'; export class Wookiee { @logged('Wookiee') hurler(): void { console.log('Rowaaaaa'); } name: string|undefined = ''; }
Nous voulons ici exĂ©cuter notre methode et logger cet appel, pour lâexemple, dans la console.
Créons un decorator (bien typé) :
export function logged<This, Args extends any[], Return>(message = "Log"){
return function actualDecorator(originalMethod: methodWithArgs<This, Args, Return>,
context: ClassMethodDecoratorContext<This, methodWithArgs<This, Args, Return>>) {
const methodName = String(context.name);
function replacementMethod(this: This, ...args: Args) {
const result = originalMethod.call(this, ...args);
console.info(`${message} ${methodName}(${args}) => ${result}`);
return result;
}
return replacementMethod;
}
}
Nous notons ici que nous exécutons notre méthode hurler grùce à :
originalMethod.call(this, ...args);
Ce qui nous permet dâĂ©crire dans la console, avant et aprĂšs.
Passons maintenant à un décorateur de champ / property.
Décorateur de validation
Ici, nous allons utiliser des meta-donnĂ©es. Pour se faire, nous allons utiliser la librairie : âreflect-metadataâ.
Note : afin que tout fonctionne, ajoute bien dans ton tsconfig cette ligne, pour prendre en compte le typage / compilation de la librairie :
"compilerOptions": {
"target": "es6",
"types": ["reflect-metadata"],
...
}
Puis dans ton fichier ts de ton decorateur, pense Ă ajouter :
import "reflect-metadata";
Et câest parti. !
PremiÚre étape
CrĂ©ons le decorator qui va ajouter une metadata âRequiredâ sur notre champs / property.
export function required<T extends Object>(
target: undefined,
context: ClassFieldDecoratorContext<T, string | undefined>
) {
return function (this: T, value: string | undefined) {
const requiredMetadataKey = 'required';
const propertyKey = String(context.name);
Reflect.defineMetadata(
requiredMetadataKey,
true,
this,
propertyKey
);
return value;
};
}
Notons ici que nous appliquons lâajout de la metada sur la property context.name, donc notre champ de classe.
DeuxiÚme étape
Créons une méthode qui va valider si notre champ de notre objet est valide ou non (vide ou renseigné) :
export function validate<T extends Object>(object: T): void {
const requiredMetadataKey = 'required';
for(const key in object) {
if (object.hasOwnProperty(key)) {
const isRequired = Reflect.getMetadata(requiredMetadataKey, object, key);
if (isRequired && !object[key]) {
throw new Error(`${key} is required`);
}
}
}
}
Note: ici nous avons créé un moteur qui ne fonctionne que pour required : il va ĂȘtre intĂ©ressant par la suite de crĂ©er des validateur custom par decorateur : par exemple, un RequiredValidator.
Nous parcourons la list des propriétés / champs de notre objet, et nous cherchons à savoir si une valeur est undefined ou null.
Si câest le cas, nous dĂ©lenchons une erreur.
Note: on pourrait aussi retourner une liste dâerreur dans un dictionnaire.
Utilisation de notre moteur
Ajoutons une classe et instancions la, avant de valider lâobjet :
import { logged, required } from './decorators';
export class Wookiee {
@logged('Wookiee')
hurler(): void {
console.log('Rowaaaaa');
}
@required
name: string|undefined = '';
}
let wookiee = new Wookiee();
wookiee.hurler();
wookiee.name = undefined;
validate(wookiee);
Et hop, une belle erreur :

Conclusion
Câest un vrai plaisir de coder des decorateurs, pour ajouter de lâAOP Ă notre code (Aspect Oriented Programation).
La façon de faire des nouveaux decorateurs ne changent pas trop dans leur appel, ni dans leur création.
Je tâinvite vraiment Ă en mettre le plus possible dans ton code ! đ
Happy coding
Stay curious !