DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for TypeScript Decorators
Yiğit Kaan Korkmaz
Yiğit Kaan Korkmaz

Posted on

TypeScript Decorators

Hello everyone! Today i will talk to you about TypeScript decorators. They're honestly just amazing and used in a variety of libraries and applications.

So, first of all, what do they do?

"A Decorator is a special declaration that can be attached to the top of classes, methods, accessors, properties, or parameters. Decorators use the form @Decorator, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration." Says the TypeScript documentation itself. I will put a link to it at the end of the article, it's a must read if you want more detailed information on decorators!

Without further a do, let's see an example of it and understand what it does:

function Configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
  };
}

function Logger(target: any, propertyKey: string) {
  console.log(`Target is: ${target}`);
  console.log(`Property Key is: ${propertyKey}`);
}

class ExampleClass {
  @Logger
  private _x: number;

  constructor(x: number) {
    this._x = x;
  }

  @Configurable(false)
  get x() {
    return this._x;
  }
}
Enter fullscreen mode Exit fullscreen mode

The code above basically makes the accessor function not configurable, and just logs the property it accesses.

But what does all that complex stuff from the documentation and this example mean? Well, it's actually very simple. A decorator is just a function with the information about the property it was put to decorate, and it will be called in runtime!

To understand it better, lets see another example of an actual decorator use:

function Sealed(constructor:Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@Sealed
class ExampleClass {
  // methods
}
Enter fullscreen mode Exit fullscreen mode

In the snippet above the Sealed decorator seals the class from adding any new functionality or removing it.

In the example below i will show you a decorator type that we can use to decorate parameters to a function. Although for this one, we will need the "reflect-metadata" package to get the parameters and define things with it.

import "reflect-metadata";
const requiredMetadataKey = Symbol("Required");

function Required(target: Object, propertyKey: string | symbol, parameterIndex: number {
  let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata( requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}

class ExampleClass {
  sayName(@Required name: string) {
    console.log(`Your name is: ${name}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

In the example above we wrote a decorator that makes the parameter it decorates required, using the metadata to add an entry.

You can decorate methods like in the first example, and you can pass variables into the decorator functions as well, making it a decorator factory to do this. Basically we make a wrapper function and take the variable from there, then use our decorator with the argument taken from the wrapper function. An example is below:

function Readonly(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.writable = value;
  };
}

class Test {
  @Readonly(true)
  sayHello() {
    console.log("Hello there!");
  }
}
Enter fullscreen mode Exit fullscreen mode

In the example above we used the Property Descriptor to modify the writable value with the given parameter to the decorator. Which makes it readonly.

Come to think of it, what are the parameters the returned function take? To simply put, target is the value we put the decorator on. Property key is the variable name we give that target. And the descriptor (which sometimes does not exist in certain values) is the description of the value that we can configure. The reason the descriptor might not exist is because certain decorators are different than others. For example, class decorators only take one argument which is the constructor. Method decorators take all three, while accessor decorators take 3 arguments as well but will not take the 3rd argument if your script target is less than ES5. Property decorators only take the first two as arguments. As for parameter decorators they take three arguments, but the last one is the parameter index and not the descriptor. Keep these in mind as you will definetly get errors if you for example give all three arguments for a class decorator.

Decorators can be used on many things. And many important libraries like class-validator and frameworks like NestJS use it.

This article was inspired very heavily from the TypeScript documentation, in order to ease the entrance to the decorators subject.

Now that you've read this article and hopefully understood what decorators do, i heavily recommend you read the official documentation! Here is the link for it: TypeScript Decorator Documentation

As always i'd love to hear your comments on the article, please do talk to me in the comments!

Have a great day!

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.