Skip to content

monade/typescript-decorators

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Typescript Decorators

This repository contains examples of Typescript decorators.

Introduction

Typescript decorators are a way to add metadata to classes, methods, properties, or parameters. They are a feature of the language that allows you to write code that modifies the behavior of a class or method. Decorators are a way to add functionality to a class or method without changing the class or method itself.

How to write a decorator

A typescript is essentially a function that takes a target object and a few metadata.

function myDecorator(target: any, /* other info */) {
    /* do stuff */
}

@myDecorator
/* the class, property or method */

Example:

function myDecorator<T extends Object>(target: T) {
    console.log(`Decorating ${target.constructor.name}`);
}

@myDecorator
class MyClass {
    /* ... */
}

// Output: Decorating MyClass

You can apply multiple decorators to the same class, property, or method.

@decorator1
@decorator2
@decorator3
/* the class, property or method */

Decorator Factories

A decorator factory is a way to pass parameters to a decorator. It's a function that returns a decorator (another function).

function myDecoratorFactory(/* decorator parameters */) {
    return function myDecorator(target: any, /* other info */) {
        /* do stuff */
    };
}

Usage:

@myDecoratorFactory(/* decorator parameters */)
/* the class, property or method */

Decorator Metadata

One of the primary use cases for decorators is to add metadata to a class, method, property, or parameter. This metadata can be used by other parts of the code to determine how to interact with the decorated element.

Typescript provides a way to access this metadata using the Reflect object. The Reflect object is available through a polyfill using the library reflect-metadata.

import 'reflect-metadata';

function myDecorator(target: any) {
    Reflect.defineMetadata('myMetadata', 'myValue', target);
}

@myDecorator
class MyClass {
    /* ... */
}

const metadata = Reflect.getMetadata('myMetadata', MyClass);

Types of Decorators

There are five types of decorators in Typescript:

  • Class decorators
  • Method decorators
  • Accessor decorators
  • Property decorators
  • Parameter decorators

Class decorators

Type Signature:

/** @param {T} constructor the class constructor */
function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T): void | T {
    /* do stuff with constructor */
    /* ... */

    /* eventually return a new constructor */
    // return class extends constructor {
    //     /* do stuff with the new constructor */
    // };
}

Usage:

@classDecorator
class MyClass {
    /* ... */
}

Examples:

  • Class Sealing: A class decorator that seals a class.
  • Class Registry: A class decorator that adds a class to a registry (a key-value map).
  • Class Reference Count: A class decorator that rewrites a class constructor to keep track of the number of instances created.
  • Class Metadata: A class decorator that adds some metadata to a class that can be retrieved later.
  • Class Singleton: A class decorator that makes a class a singleton.
  • Class Method Logging: A class decorator that logs every method call by wrapping them.

Caveats

  • When re-writing a class (like in Class Method Logging), you need to be aware that the new class will have a different name and prototype than the original class.
  • Take care of the typing: if you extend the original class, the new methods won't be visible in the type system.
  • In the example Class Registry, be aware that Tree-shaking may remove the class from the registry if it's not used anywhere else in the code. You may need to explicitly import the whole module in the entry file to prevent this.

Method decorators

Type Signature:

/** @param {T} target the class prototype */
/** @param {string} propertyKey the name of the method */
/** @param {PropertyDescriptor} descriptor the method descriptor */
function methodDecorator<T extends Object>(target: T, propertyKey: string, descriptor: PropertyDescriptor) {
    /* do stuff with target, propertyKey, and descriptor */
    /* ... */

    /* eventyally modify the descriptor */
    // descriptor.value = function () {
    //     /* do stuff with the original method */
    //     /* ... */
    // };
}

Usage:

class MyClass {
    @methodDecorator
    myMethod() {
        /* ... */
    }
}

Examples:

  • Method Typecheck: A method decorator that type-checks the arguments and return value of a method.
  • Method Logging: A method decorator that wraps a method to catch and log errors that occur during its execution.
  • Method Timeout: A method decorator that introduces a timeout to an async method.

Accessor decorators

Signature:

/** @param {T} target the class prototype */
/** @param {string} propertyKey the name of the accessor */
/** @param {PropertyDescriptor} descriptor the accessor descriptor */
function accessorDecorator<T extends Object>(target: T, propertyKey: string, descriptor: PropertyDescriptor) {
    /* do stuff with target, propertyKey, and descriptor */
    /* ... */

    /* eventyally modify the descriptor */
    // descriptor.get = function () {
    //     /* do stuff with the original getter */
    //     /* ... */
    // };
}

Usage:

class MyClass {
    @accessorDecorator
    get myProperty() {
        /* ... */
    }
}

Examples:

  • Accessor Cache: An accessor decorator that caches the result of a getter. It also show how to invalidate the cache.
  • Accessor Descriptors: An accessor decorator that interacts with the property descriptor of a getter and changes its behavior. (i.e. making it read-only, enumerable, etc.)
  • Accessor Logging: An accessor decorator that logs the access to a getter.

Property decorators

Signature:

/** @param {T} target the class prototype */
/** @param {string} propertyKey the name of the property */
function propertyDecorator<T extends Object>(target: T, propertyKey: string) {
    /* do stuff with target and propertyKey */
    /* ... */
}

Usage:

class MyClass {
    @propertyDecorator
    myProperty: string;
}

Examples:

Pro Tip:

The example Property Metadata Typecheck can be achieved with no code by setting the emitDecoratorMetadata option to true in the tsconfig.json file.

{
    "compilerOptions": {
        "emitDecoratorMetadata": true
    }
}

Caveats In the Property Dependency Injection example, may not work if you set the target to ES2022 in the tsconfig.json file. You may need to set useDefineForClassFields to false to make it work.

{
    "compilerOptions": {
        "target": "ES2022",
        "useDefineForClassFields": false
    }
}

This is because the useDefineForClassFields option changes the way class fields are defined.

Parameter decorators

Signature:

/** @param {T} target the class prototype */
/** @param {string} propertyKey the name of the method */
/** @param {number} parameterIndex the index of the parameter */
function parameterDecorator<T extends Object>(target: T, propertyKey: string, parameterIndex: number) {
    /* do stuff with target, propertyKey, and parameterIndex */
    /* ... */
}

Usage:

class MyClass {
    myMethod(@parameterDecorator myParameter: string) {
        /* ... */
    }
}

Caveats

Parameter decorators can't modify directly the behavior of a method. They can only be used to add metadata to a parameter. You may need to use them in conjunction with other decorators to achieve the desired behavior. See the examples below.

Examples:

Functional Javascript and Decorators

Unfortunately Typescript decorators unfortunately don't support decorating a function directly.

function myDecorator() {
    /* ... */
}

// This won't work
@myDecorator
function myFunction() {

}

However, you can achieve similar functionality by using higher-order functions.

function myDecorator(fn: Function) {
    return function (...args: any[]) {
        /* do stuff before calling the function */
        /* ... */

        const result = fn(...args);

        /* do stuff after calling the function */
        /* ... */

        return result;
    };
}

const myFunction = myDecorator(function () {
    /* ... */
});

Examples:

  • Function decorator: Shows how to achieve similar functionality to a function decorator using higher-order functions.
  • Function fancier decorator: Shows a pattern that helps achieving more complex functionality by introducing a decorate function.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published