Qu'est ce qu'un décorateur ?

Une simple fonction JavaScript !

Pour plus de détails, et d'après la documentation TypeScript :

Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members. Decorators are a stage 2 proposal for JavaScript and are available as an experimental feature of TypeScript.

Les décorateurs permettent de surcharger la logique des méthodes, classes, propriétés et paramètres de fonctions.

Si vous avez déjà travaillé avec Angular, vous avez sûrement déjà utilisé des décorateurs : @Input(), @ViewChild(), @Injectable()…

Pour pouvoir les utiliser, vous devez préciser experimentalDecorators: true dans le fichier config.json.

A quoi ça ressemble ?

A une simple fonction JavaScript😄 :

function logger(target) {
  // do something with 'target' ...
}

Nous pouvons également l'écrire en tant que "Factory".

Une Factory est une fonction qui retourne l'expression appelée par le décorateur lors de l'exécution du code, et permet de lui donner des arguments.

function logger(type: string) {
  // this is the decorator factory, it sets up
  // the returned decorator function
  return function (target) {
    // this is the decorator
    // do something with 'target' and 'value'...
  };
}

Exemple : Confirmation dialogs

Dans les applications web, nous avons souvent (toujours ?) besoin de dialogs de confirmation afin d'empêcher les utilisateurs d'effectuer des actions importantes par erreur (suppression, annulation…).

Et nous finissons souvent par avoir plusieurs méthodes ressemblant à cela :

onDelete(ids: number[]): void {
    this.confirmationDialogService.open(
        {
            message: 'You are about to delete item(s). Continue?',
            accept: () => this.deleteItems(ids)
        }
    );
}

Ne serait-il pas satisfaisant de pouvoir écrire quelque chose comme ça ?

@Confirmable // Custom decorator here
onDelete(ids: number[]): void {
    this.deleteItems(ids); // only declare the method to apply if user confirms
}

Ca tombe bien, c'est justement ce que nous allons faire 🚀!

Implémentation

Tout d'abord créons un fichier confirmable.decorator.ts.

Dans ce fichier, nous allons exporter une fonction que nous allons appeler Confirmable.

export function Confirmable (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    // cache the original method for later useconst originalMethod = descriptor.value;
    // we write a new implementation for the method
    descriptor.value = async function (...args) {
        const message = 'Are you sure that you want to perform this action?';

        // ConfirmationDialogService must be injected to be used.// AppModule needs to be configured with injector, more on that later (*).
        const confirmationDialogService = AppModule.injector.get<ConfirmationDialogService>(
            ConfirmationDialogService
        );
        const res: boolean = await confirmationDialogService.open(message);
        if (res){ // if user clicked yes,// we run the original method with the original argumentsconst result = originalMethod.apply(this, args);
        // and return the result
      return result;
    }
    };
    return descriptor;
  };

Vous pouvez essayer en l'état, cela fonctionne déjà :

@Confirmable
onDelete(ids: number[]): void {
    this.deleteItems(ids);
}

(*) : afin de pouvoir injecter le confirmationDialogService dans notre décorateur, il faudra écrire cela dans AppModule (ou le module dans lequel le service est provided) :

export class AppModule {
&nbsp; static injector: Injector;
&nbsp; 
&nbsp; constructor(injector: Injector) {
&nbsp; &nbsp; AppModule.injector = injector;
&nbsp; }
}

Message de confirmation personnalisable

Mais comment pouvons nous faire pour afficher des messages de confirmation différents selon la méthode décorée ?

Pour cela, nous allons transformer notre nouveau décorateur en une factory !

const DEFAULT_MESSAGE = 'Are you sure that you want to perform this action?';

export function Confirmable(message: string = DEFAULT_MESSAGE) {
    return (target: Object, propertyKey: string, descriptor: PropertyDescriptor) => {
        const originalMethod = descriptor.value;
        descriptor.value = async function (...args) {
            const confirmationDialogService = AppModule.injector.get<ConfirmationDialogService>(
                ConfirmationDialogService
            );
            const res = await confirmationDialogService.open(message);
            if (res) {
                const result = originalMethod.apply(this, args);
                return result;
            }
        };
        return descriptor;
    };
}

Et voilà ! Nous pouvons désormais définir un message personnalisé lors de l'utilisation du décorateur :

@Confirmable('You are going to delete item(s). Continue?')
onDelete(ids: number[]): void {
    this.deleteItems(ids);
}

Ce sera tout pour aujourd'hui, j'espère que vous aurez appris quelque chose d'utile😊.