Type­Script dec­o­ra­tors offer an efficient and straight­for­ward way to add extra func­tion­al­i­ty to objects without altering the original code. They can be applied to classes, methods, prop­er­ties, accessors, and pa­ra­me­ters, lever­ag­ing an­no­ta­tions and metadata.

What are Type­Script dec­o­ra­tors and what are they used for?

The principle of Type­Script dec­o­ra­tors is not fun­da­men­tal­ly new. Similar features can be found in other pro­gram­ming languages, such as at­trib­ut­es in C#, dec­o­ra­tors in Python or an­no­ta­tions in Java. This is a way of extending the func­tion­al­i­ty of an object without changing the source code. Type­Script has also been working with this approach for some time. Although most browsers do not (yet) support Type­Script dec­o­ra­tors, it is still worth trying out this approach and its pos­si­bil­i­ties. Since version 5.0, the use of dec­o­ra­tors has been massively sim­pli­fied once again.

Type­Script dec­o­ra­tors are used to add an­no­ta­tions and ad­di­tion­al metadata for Type­Script classes and elements. In addition to the classes, methods, prop­er­ties, access methods and pa­ra­me­ters can also be changed. The latter can be checked, and the values retrieved. This is also a major dif­fer­ence between Type­Script dec­o­ra­tors and their equiv­a­lent for JavaScript.

What is the syntax for dec­o­ra­tors?

By adding Type­Script dec­o­ra­tors to an object, you are es­sen­tial­ly invoking a function that runs without altering the source code. This enhances func­tion­al­i­ty while main­tain­ing clean and organized code. The basic syntax is as follows:

@nameOfTheDecorator
type­script

You can create this function with either two or three pa­ra­me­ters. The syntax for the function with three pa­ra­me­ters 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');
    }
}
type­script

The in­di­vid­ual Type­Script dec­o­ra­tors com­po­nents are made up as follows:

  • target: Refers to the object that the decorator is assigned to.
  • propertyKey: This is a string rep­re­sent­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 Type­Script dec­o­ra­tors with two pa­ra­me­ters:

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

In this instance, dec­o­ra­tors in Type­Script were used on a class.

What are the various types of dec­o­ra­tors?

We will explore different types of Type­Script dec­o­ra­tors in detail, each with its own unique char­ac­ter­is­tics:

  • Class dec­o­ra­tors
  • Method dec­o­ra­tors
  • Property dec­o­ra­tors
  • Accessor dec­o­ra­tors
  • Parameter dec­o­ra­tors

How to use Type­Script dec­o­ra­tors for classes

If you want to customize the prop­er­ties of a class and change its con­struc­tor, methods or prop­er­ties, you can do so with Type­Script dec­o­ra­tors. You receive the con­struc­tor 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 = "325 Lafayette Rd";
p.residence = "New Jersey";
type­script

In the next step, we will in­cor­po­rate Type­Script dec­o­ra­tors to enhance func­tion­al­i­ty without retroac­tive­ly 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­al­ly, @enumerable will be used for enu­mer­a­tions, and @deprecated will indicate outdated inputs. Our first task will be to define these dec­o­ra­tors:

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.`);
}
type­script

After using Type­Script dec­o­ra­tors, 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";
type­script

Method Type­Script dec­o­ra­tors

Type­Script dec­o­ra­tors can also be used for methods. Ex­cep­tions are de­c­la­ra­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}`;
    }
}
type­script

Property Type­Script dec­o­ra­tors

Type­Script dec­o­ra­tors for class prop­er­ties (property dec­o­ra­tors) take two pa­ra­me­ters, which are the class’s con­struc­tor 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";
}
type­script

Accessor Type­Script dec­o­ra­tors

Accessor dec­o­ra­tors work on a principle similar to Property dec­o­ra­tors, but with one key dif­fer­ence: they include an ad­di­tion­al third parameter. In this context, that parameter is the Property De­scrip­tor for a customer. When you use an Accessor Decorator to set a value, it updates the Property De­scrip­tor ac­cord­ing­ly. 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;
    }
}
type­script

Here’s how to use the decorator:

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

Parameter Type­Script dec­o­ra­tors

Type­Script Parameter dec­o­ra­tors also receive three arguments: the class con­struc­tor 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­i­da­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}`);
}
type­script

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

class Example {
    testMethod(param0: any, @print param1: any) {}
}
type­script
Tip

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

Go to Main Menu