When it comes to decorating, I’m about as useful as a flashlight in broad daylight 🔦!

But I’m not talking about that kind of decorating, I’m talking about JavaScript method decorators.

Let’s take a look!👇

Background

Decorators are a powerful feature in JavaScript/Typescript that allow you to modify the behavior of classes, methods, and properties. They provide a way to add, alter, or enhance the functionality of existing code. In this article, we’ll explore the background of decorators in JavaScript/Typescript and provide examples of how to use them.

Decorators are not a new concept in programming. They have been a part of other programming languages like Python and Java for a while. In JavaScript, decorators were introduced as an experimental feature in ECMAScript (ES) proposals, specifically in the ES2016 and ES2017 iterations. They have since become widely supported in modern JavaScript/Typescript environments. I mostly use decorators throughout game development as it usually requires functional driven repetitive tasks, more on that another time.

Simple Logger Decorator

Let’s start with a simple example of a decorator that logs “Hello, World!” to the console when applied to a class method. Decorators are functions that are applied to classes, methods, or properties to modify their behavior.

function logHello(target, name, descriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args) {
    console.log("Hello, World!");
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

class Greeting {
  @logHello
  sayHello() {
    console.log("Greetings!");
  }
}

const greeter = new Greeting();
greeter.sayHello(); // Output: "Hello, World!" followed by "Greetings!"

In this example, the @logHello decorator is applied to the sayHello method of the Greeting class. When we call greeter.sayHello(), it logs “Hello, World!” before executing the original method.

Lombok-Like Features for TypeScript

Decorators can be used to mimic features similar to those provided by libraries like Lombok in Java. Lombok simplifies code by generating common boilerplate code for classes, such as getters and setters. We can achieve similar functionality in TypeScript using decorators.

function Getter(target, key) {
  target[`get${key.charAt(0).toUpperCase() + key.slice(1)}`] = function (this) {
    return this[key];
  };
}

function Setter(target, key) {
  target[`set${key.charAt(0).toUpperCase() + key.slice(1)}`] = function (
    this,
    newVal
  ) {
    this[key] = newVal;
  };
}

export class MyClass {
  @Getter
  private myValue = '123';

  @Getter
  @Setter
  private myOtherValue = 'ABC';
}

const myInstance = new MyClass();

console.log(myInstance.getMyValue()); // Output: "123"
console.log(myInstance.setMyValue()); // Output: Error: setMyValue is not a function

console.log(myInstance.getMyOtherValue()); // Output: "ABC"

myInstance.setMyOtherValue('CBA');
console.log(myInstance.getMyOtherValue()); // Output: "CBA"

In this code, we define @Getter and @Setter decorators that mimic getter and setter methods, similar to what Lombok does in Java. These decorators allow us to define and use properties with the convenience of getters and setters.

Conclusion

Decorators are a nice and easy way to abstract functionality that doesn’t need to be explicitly interpreted. Think of them as friendly helpers that make your code more maintainable and easier to read although beware not to lean too heavily on them for complex situations. For example, I tend to stick to using decorators for unrelated functionality that I am developing. Don’t let them be a bridge to patterns you are following, rather implicit (explicitly defined 😀) helpers.