TypeScript decorators offer an efficient and straightforward way to add extra functionality to objects without altering the original code. They can be applied to classes, methods, properties, accessors, and parameters, leveraging annotations and metadata.

What are TypeScript decorators and what are they used for?

The principle of TypeScript decorators is not fundamentally new. Similar features can be found in other programming languages, such as attributes in C#, decorators in Python or annotations in Java. This is a way of extending the functionality of an object without changing the source code. TypeScript has also been working with this approach for some time. Although most browsers do not (yet) support TypeScript decorators, it is still worth trying out this approach and its possibilities. Since version 5.0, the use of decorators has been massively simplified once again.

TypeScript decorators are used to add annotations and additional metadata for TypeScript classes and elements. In addition to the classes, methods, properties, access methods and parameters can also be changed. The latter can be checked, and the values retrieved. This is also a major difference between TypeScript decorators and their equivalent for JavaScript.

What is the syntax for decorators?

By adding TypeScript decorators to an object, you are essentially invoking a function that runs without altering the source code. This enhances functionality while maintaining clean and organized code. The basic syntax is as follows:

@nameOfTheDecorator
typescript

You can create this function with either two or three parameters. The syntax for the function with three parameters looks like this:

function decoratorFunction(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log(`Decorating ${propertyKey} of class ${target.constructor.name}`);
}
class MyClass {
    @decoratorFunction
    myMethod() {
        console.log('Executing myMethod');
    }
}
typescript

The individual TypeScript decorators components are made up as follows:

  • target: Refers to the object that the decorator is assigned to.
  • propertyKey: This is a string representing the name of the class where the decorator is applied, which could be methods or properties.
  • descriptor: Stores additional details about the object the decorator is applied to, such as properties like value, writable, enumerable, or configurable.

Here is the syntax of TypeScript decorators with two parameters:

function decoratorFunction(target: any) {
    console.log(`Decorating ${target.name}`);
}
@decoratorFunction
class MyClass {
}
typescript

In this instance, decorators in TypeScript were used on a class.

What are the various types of decorators?

We will explore different types of TypeScript decorators in detail, each with its own unique characteristics:

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

How to use TypeScript decorators for classes

If you want to customize the properties of a class and change its constructor, methods or properties, you can do so with TypeScript decorators. You receive the constructor as the first parameter as soon as you “decorate” the class with a function. This is an example of code where we are working with a customer list. It has some private and some public properties:

class Customers {
    private static userType: string = "Generic";
    private _email: string;
    public customerName: string;
    public street: string = "";
    public residence: string = "";
    public country: string = "";
    constructor(customerName: string, email: string) {
        this.customerName = customerName;
        this._email = email;
    }
    static get userType() {
        return Customers.userType;
    }
    get email() {
        return this._email;
    }
    set email(newEmail: string) {
        this._email = newEmail;
    }
    address(): string {
        return `${this.street}\n${this.residence}\n${this.country}`;
    }
}
const p = new Customers("exampleCustomer", "name@example.com");
p.street = "325 Lafayette Rd";
p.residence = "New Jersey";
typescript

In the next step, we will incorporate TypeScript decorators to enhance functionality without retroactively modifying the source code. For the “Customer” class, we will apply the @frozen decorator, which prevents objects from being altered afterward. We will use @required for certain properties to mandate explicit input. Additionally, @enumerable will be used for enumerations, and @deprecated will indicate outdated inputs. Our first task will be to define these decorators:

function frozen(constructor: Function) {
    Object.freeze(constructor);
    Object.freeze(constructor.prototype);
}
function required(target: any, propertyKey: string) {
    // Logic for required decorator
}
function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}
function deprecated(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.warn(`The method ${propertyKey} is deprecated.`);
}
typescript

After using TypeScript decorators, the final code looks like this:

@frozen
class Customers {
    private static userType: string = "Generic";
    @required
    private _email: string;
    @required
    public customerName: string;
    public street: string = "";
    public residence: string = "";
    public country: string = "";
    constructor(customerName: string, email: string) {
        this.customerName = customerName;
        this._email = email;
    }
    @enumerable(false)
    get userType() {
        return Customers.userType;
    }
    get email() {
        return this._email;
    }
    set email(newEmail: string) {
        this._email = newEmail;
    }
    @deprecated
    address(): string {
        return `${this.street}\n${this.residence}\n${this.country}`;
    }
}
const p = new Customers ("exampleCustomer", "name@example.com");
p.street = "325 Lafayette Rd";
p.residence = "New Jersey";
typescript

Method TypeScript decorators

TypeScript decorators can also be used for methods. Exceptions are declaration files, overloading or the “declare” class. In the following example, we are going to use @enumerable as a decorator for the getName method in the “Person” class:

const enumerable = (value: boolean) => {
    return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
        propertyDescriptor.enumerable = value;
    }
}
class Person {
    firstName: string = "Julia"
    lastName: string = "Brown"
    @enumerable(true)
    getName () {
        return `${this.firstName} ${this.lastName}`;
    }
}
typescript

Property TypeScript decorators

TypeScript decorators for class properties (property decorators) take two parameters, which are the class’s constructor function and the name of the property. In the example below, we use the decorator to display the name of a property (such as the customer name):

const printPropertyName = (target: any, propertyName: string) => {
    console.log(propertyName);
};
class Customers {
    @printPropertyName
    name: string = "Julia";
}
typescript

Accessor TypeScript decorators

Accessor decorators work on a principle similar to Property decorators, but with one key difference: they include an additional third parameter. In this context, that parameter is the Property Descriptor for a customer. When you use an Accessor Decorator to set a value, it updates the Property Descriptor accordingly. In the following code, for example, the boolean value (true or false) of enumerable is modified. Here’s the starting point:

const enumerable = (value: boolean) => {
    return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
        descriptor.enumerable = value;
    }
}
typescript

Here’s how to use the decorator:

class Customers {
    firstName: string = "Julia";
    lastName: string = "Brown";
    @enumerable(true)
    get name() {
        return `${this.firstName} ${this.lastName}`;
    }
}
typescript

Parameter TypeScript decorators

TypeScript Parameter decorators also receive three arguments: the class constructor function, the name of the method, and the index of the parameter. However, the parameter itself cannot be modified, meaning this decorator is limited to validation purposes. To access the parameter index, you can use the following code:

function print(target: Object, propertyKey: string, parameterIndex: number) {
    console.log(`Decorating param ${parameterIndex} from ${propertyKey}`);
}
typescript

If you then apply the Decorator parameter, this is the code:

class Example {
    testMethod(param0: any, @print param1: any) {}
}
typescript
Tip

Ideal for static websites and apps alike: With Deploy Now from IONOS, you benefit from simple staging, a quick setup and perfectly coordinated workflows. Find the right plan to suit your needs!

Was this article helpful?
Go to Main Menu