TypeScript dec­or­at­ors offer an efficient and straight­for­ward way to add extra func­tion­al­ity to objects without altering the original code. They can be applied to classes, methods, prop­er­ties, accessors, and para­met­ers, lever­aging an­nota­tions and metadata.

What are TypeScript dec­or­at­ors and what are they used for?

The principle of TypeScript dec­or­at­ors is not fun­da­ment­ally new. Similar features can be found in other pro­gram­ming languages, such as at­trib­utes in C#, dec­or­at­ors in Python or an­nota­tions in Java. This is a way of extending the func­tion­al­ity 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 dec­or­at­ors, it is still worth trying out this approach and its pos­sib­il­it­ies. Since version 5.0, the use of dec­or­at­ors has been massively sim­pli­fied once again.

TypeScript dec­or­at­ors are used to add an­nota­tions and ad­di­tion­al metadata for TypeScript classes and elements. In addition to the classes, methods, prop­er­ties, access methods and para­met­ers can also be changed. The latter can be checked, and the values retrieved. This is also a major dif­fer­ence between TypeScript dec­or­at­ors and their equi­val­ent for JavaS­cript.

Managed Nextcloud from IONOS Cloud
Work together in your own cloud
  • Industry-leading security
  • Com­mu­nic­a­tion and col­lab­or­a­tion tools
  • Hosted and developed in Europe

What is the syntax for dec­or­at­ors?

By adding TypeScript dec­or­at­ors to an object, you are es­sen­tially invoking a function that runs without altering the source code. This enhances func­tion­al­ity while main­tain­ing clean and organised code. The basic syntax is as follows:

@nameOfTheDecorator
typescript

You can create this function with either two or three para­met­ers. The syntax for the function with three para­met­ers 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 in­di­vidu­al TypeScript dec­or­at­ors com­pon­ents are made up as follows:

  • target: Refers to the object that the decorator is assigned to.
  • propertyKey: This is a string rep­res­ent­ing the name of the class where the decorator is applied, which could be methods or prop­er­ties.
  • descriptor: Stores ad­di­tion­al details about the object the decorator is applied to, such as prop­er­ties like value, writable, enumerable, or configurable.

Here is the syntax of TypeScript dec­or­at­ors with two para­met­ers:

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

In this instance, dec­or­at­ors in TypeScript were used on a class.

What are the various types of dec­or­at­ors?

We will explore different types of TypeScript dec­or­at­ors in detail, each with its own unique char­ac­ter­ist­ics:

  • Class dec­or­at­ors
  • Method dec­or­at­ors
  • Property dec­or­at­ors
  • Accessor dec­or­at­ors
  • Parameter dec­or­at­ors

How to use TypeScript dec­or­at­ors for classes

If you want to customise the prop­er­ties of a class and change its con­struct­or, methods or prop­er­ties, you can do so with TypeScript dec­or­at­ors. You receive the con­struct­or 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 prop­er­ties:

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 = "32 High Street";
p.residence = "Bournemouth";
typescript

In the next step, we will in­cor­por­ate TypeScript dec­or­at­ors to enhance func­tion­al­ity without ret­ro­act­ively 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 prop­er­ties to mandate explicit input. Ad­di­tion­ally, @enumerable will be used for enu­mer­a­tions, and @deprecated will indicate outdated inputs. Our first task will be to define these dec­or­at­ors:

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 dec­or­at­ors, 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 = "32 High Street";
p.residence = "Bournemouth";
typescript

Method TypeScript dec­or­at­ors

TypeScript dec­or­at­ors can also be used for methods. Ex­cep­tions are de­clar­a­tion files, over­load­ing 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 dec­or­at­ors

TypeScript dec­or­at­ors for class prop­er­ties (property dec­or­at­ors) take two para­met­ers, which are the class’s con­struct­or 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 dec­or­at­ors

Accessor dec­or­at­ors work on a principle similar to Property dec­or­at­ors, but with one key dif­fer­ence: they include an ad­di­tion­al 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 ac­cord­ingly. In the following code, for example, the boolean value (true or false) of enu­mer­able 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 dec­or­at­ors

TypeScript Parameter dec­or­at­ors also receive three arguments: the class con­struct­or 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 val­id­a­tion 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 co­ordin­ated workflows. Find the right package to suit your needs!

Go to Main Menu