How I Fell in Love with JS Decorators

One of the recent addiction for our team was the usage of Decorators which are proposed in the new ES7. In this article, I am going to strip Decorators for you so that you can see the beauty of it!

If you are a coming from a Python background, the syntax and the concepts will be familiar to you. It would be relatively easy for you to pick things up. But it was not the same case for me. I have no background in any other languages. I started to code for fun. The first language I learned was javascript. Thanks to w3Schools. I heard the term Decorators back in 2015 when typescript came out. That was Greek for me at that time, so I just ignored it.

A couple of months back, I got addicted to typescript and Angular 2. I was enjoying my coding time with all new typings in my projects.

When I began Angular 2, I ignored those ‘@’ syntaxes present everywhere in angular so that I could learn the framework faster. I read about those syntaxes here and there, but I never made any efforts to learn them.

(Let me just not damage my brain now)

And that one fine Monday morning came, I was curious to learn this new ES7 feature called Decorators. I started to learn it. It was worth it… Yes. I fell in love. I’ll promise you that once you learn it, it is so seductive in nature that you will try to use it everywhere you find it can be used.

As there are very fewer resources out there about JS Decorators, I decided to write down my Journey of falling in love with decorators.

(I cannot resist but fall in love)

First Things First: What the hell these Decorators are?

This is what you get when you search for Decorator Pattern.

(Source: Wikipedia) (Don’t worry if you didn’t get the gist of it)

Even more intuitive example would be the following.

(Source: https://sourcemaking.com/design_patterns/decorator)

WeaponAccessory is the decorator here. They add extra functionality to the BaseWeapon.

Don’t worry if you are still not clear, let me break down it for you. You will understand it further when you start creating decorators with me. Let me put it this way…

Decorators are functions that give additional super-powers to different function
(Decorators Give Superpower to a piece of function)

The Gist:
Just think of it as a wrapper for an object!

  • The actual object resides inside the wrapper
  • if you want to access the object you need to go through the wrapper

Eg:

A Gun -> A Gun attached with a silencer
A Box -> A Box wrapped with a wrapping paper
A JS Function -> A JS Function which does additional work than what is defined.
A JS Object -> A JS Object that has additional properties than the ones which are defined.

Decorators make it possible to annotate and modify classes and properties at design time.

Points to Note

  • Decorators are just functions

How do I use this phenomenon in JS?

Decorators are a proposed feature in ES7. ES7 is not fully documented yet. But that doesn’t mean that we can’t use them. Thanks to the compilers out there: Babel and Typescript to the Rescue.

I will be using Typescript for the examples in this article.

How do I set it up?

In your tsconfig.json , you need to set experimentalDecorators and emitDecoratorMetadata to true. and you are good to start

{
"compilerOptions": {
"module": "commonjs",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "es2015"
},
"exclude": [
"node_modules"
]
}

(Hint: Make sure that you have the latest Typescript Version)


Shut up and watch

To make things easier, I followed a learning technique — “Shut up and code”

I am going to type in some Greek. Focus on the output, you can learn that Greek later.

function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor): any {
var oldValue = descriptor.value;
    descriptor.value = function() {
console.log(`Calling "${propertyKey}" with`, arguments,target);
let value = oldValue.apply(null, [arguments[1], arguments[0]]);
      console.log(`Function is executed`);
return value + "; This is awesome";
};
    return descriptor;
}
  class JSMeetup {
speaker = "Ruban";
//@leDecorator
welcome(arg1, arg2) {
console.log(`Arguments Received are ${arg1} ${arg2}`);
return `${arg1} ${arg2}`;
}
}
  const meetup = new JSMeetup();
  console.log(meetup.welcome("World", "Hello"));

Once you run the above code, you will get the following output.

Arguments Received are World Hello
World Hello

Now Uncomment the line @leDecorator and now run it…

Calling "welcome" with { '0': 'World', '1': 'Hello' } JSMeetup {}
Arguments Received are Hello World
Function is executed
Hello World; This is awesome

As you can see you just annotated a function with a decorator and now the behavior of the function is different.

Let’s Try to understand what is going here.

(Let’s dive deep in!)

Types of Decorators

  1. Method Decorator
  2. Property Decorators
  3. Class Decorator
  4. Parameter Decorator

Now, let’s look at each type of decorators.


Method Decorators

Method Decorators are the first type of decorators that you are going to write with me and I think you would get the essence of decorators once learn the method decorators. By using Method Decorators you would gain control over the inputs of the method and output of the method.

Decorator Signature

MethodDecorator = <T>(target: Object, key: string, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | Void;

Pre-Requisite

Before you write can write the first decorator, let’s learn what helps you learn the decorator.

Let’s understand the parameters of the decorator

  • target -> The object being decorated
  • key -> The name of the method being decorated.
  • descriptor -> The property descriptor of the given property. You can see the property descriptor by invoking the function Object.getOwnPropertyDescriptor()

Property Descriptors

ref: getOwnPropertyDescriptor

To get an intuitive idea about the property descriptors, Take a look at the following console code.

var o, d;
var o = { get foo() { return 17; }, bar:17, foobar:function(){return "FooBar"} };
d = Object.getOwnPropertyDescriptor(o, 'foo');
console.log(d);
d = Object.getOwnPropertyDescriptor(o, 'bar');
console.log(d);
d = Object.getOwnPropertyDescriptor(o, 'foobar');
console.log(d);

Here we have defined, an object named o with the properties foo , barand foobar . Then we are logging the each properties descriptor with the method Object.getOwnPropertyDescriptor()

Let’s get a sneak peak of what value , enumerable , configurableand writable mean.

  • value →Actual value or the function that is processed by the method/property
  • enumerable -> Whether the property should be enumerable (Whether it should be there in (for x in obj)loop)
  • configurable →Whether the property can be configured
  • writable -> Whether the property is writable.

As you can see, all properties and methods have their own descriptors. Decorators essentially modify the descriptor to provide additional functionalities. Since you understood how the methods and properties are defined it’s time for you to go make some changes to it using decorators.

Example Method Decorator

In the following example, we are going to modify the input and the output of a method.

function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor): any {
var oldValue = descriptor.value;
    descriptor.value = function() {
console.log(`Calling "${propertyKey}" with`, arguments,target);
// Executing the original function interchanging the arguments
let value = oldValue.apply(null, [arguments[1], arguments[0]]);
//returning a modified value
return value + "; This is awesome";
};
    return descriptor;
}
  class JSMeetup {
speaker = "Ruban";
//@leDecorator
welcome(arg1, arg2) {
console.log(`Arguments Received are ${arg1}, ${arg2}`);
return `${arg1} ${arg2}`;
}
}
  const meetup = new JSMeetup();
  console.log(meetup.welcome("World", "Hello"));

If you run the above code, without the decorator, the output would be

Arguments Received are World, Hello
World Hello

If you run the above code, with the decorator the output would be

Arguments Received are Hello, World
Hello World; This is awesome

What is happening?

in the decorator function, the descriptor has certain properties associated with it. (Refer: pre-requisite). First, we are storing the original function in a variable (var oldValue = descriptor.value; ) . Then, we are modifying the value of the descriptor and then returning the descriptor. As you can see inside the modified function we could execute the original function with modified arguments and return a modified output.

Isn’t this Amazing?

Decorators wrap your methods, By using Decorators, you essentially have the power to manipulate an input of a function and output of a function. That means you can do magic with it!

Points to Ponder

  • Decorators are called when the class is declared — not when an object is instantiated.
  • Method Decorators return a value
  • It’s better to store the existing descriptor value and return a new value. The reason behind this is that if you modify the descriptor, the other decorators which are which are modifying the behavior would not get access to the original decorator. (We will look into using multiple decorators shortly)
  • Do not use arrow syntax when setting the descriptor’s value.

What can you do with this?

  • logging
  • Apply Formatting
  • Apply Permission Checks
  • Block Overriding of methods
  • Timing Functions
  • Rate-Limiting
  • Whatever you thought that that cannot be done while you were having breakfast
  • and the list goes on…

Hooray! Now you have written your first Decorator.


Before we move on to the other types of decorators lets look at the below concepts

Decorator Factory

You can make use of decorator factories to customize the decorator that is being applied.

Example Decorator Factory

function leDecoratorFactory(randomData: string) {
return (target, propertyKey: string, descriptor: PropertyDescriptor): any => {
var oldValue = descriptor.value;
      descriptor.value = function () {
console.log(`Calling "${propertyKey}" with`, arguments, target);
let value = oldValue.apply(null, [arguments[1], arguments[0]]);
        console.log(`Function is executed`);
return value + "; This is awesome; " + randomData;
}
return descriptor;
}
}
  class JSMeetup {
speaker = "Ruban";
@leDecoratorFactory("Extra Data")
welcome(arg1, arg2) {
console.log(`Arguments Received are ${arg1} ${arg2}`);
return `${arg1} ${arg2}`;
}
}
  const meetup = new JSMeetup();
  console.log(meetup.welcome("World", "Hello"));

The output of the above code would be the following,

Arguments Received are Hello World
Hello World; This is awesome; Extra Data

What is happening here?

Decorator Factory takes custom arguments and returns the decorator function. Thanks to the closures, now the decorator can use the data passed into the factory function.

What can you do with it?

  • You dont have to craete multiple similar decorators
  • You could have a single point to access all different types of decorators

Decorator Composition

Multiple decorators can be applied to a method/class/property.

@decorator_1
@decorator_2
@decorator_3("Some Data")
class JSMeetup {
public myName = "Ruban";
constructor() {
}
greet() {
return "Hi, I'm " + this.myName;
}
}

The Gist: Wrapping a gift, putting it in a box, and wrapping the box.

(oooohhh ohhhh!)

Points to Ponder

  • Decorators gets evaluated from top down
  • Decorators gets executed from bottom up

More Reference can be found in https://www.typescriptlang.org/docs/handbook/decorators.html#decorator-evaluation

Property Decorators

Property decorators are very similar to method decorators. We will get the power to redefine the getters, setters and other properties such as enumerable, configurable etc. Let’s dive into property decorators

Decorator Signature

PropertyDecorator = (target: Object, key: string) => void;

Pre-Requisite

For us to get a deeper understanding and write property decorators, we need to understand Object.defineProperty function.

To speed things up, let us look at the usage of Object.defineProperty() function.

Object.defineProperty(o, 'myProperty', {
get: function () {
return this['myProperty'];
},
set: function (val) {
this['myProperty'] = val;
},
enumerable:true,
configurable:true
});

When you were using the function Object.getOwnPropertyDescriptor() , you would have noticed that properties and methods of a class have their own properties. We use Object.defineProperty() to define them. I guess the dots would now connect.

now let’s write a simple property descriptor

Example Property Decorator in Action

The following property descriptor will override the property myName and returns my real name all the time, along with the name given in the class.

function realName(target, key: string): any {
// property value
var _val = this[key];
    // property getter
var getter = function () {
return "Ragularuban(" + _val + ")";
};
    // property setter
var setter = function (newVal) {
_val = newVal;
};
    // Create new property with getter and setter
Object.defineProperty(target, key, {
get: getter,
set: setter
});
}
  class JSMeetup {
//@realName
public myName = "Ruban";
constructor() {
}
greet() {
return "Hi, I'm " + this.myName;
}
}
  const meetup = new JSMeetup();
console.log(meetup.greet());
meetup.myName = "Ragul";
console.log(meetup.greet());

If you run the above code, without the decorator, the output would be

Hi, I’m Ruban
Hi, I’m Ragul

If you run the code, with the decorator, the output would be

Hi, I’m Ragularuban(Ruban)
Hi, I’m Ragularuban(Ragul)

What is happening?

Property decorators take two parameters(target, key). We make use of them to redefine the property of the object using the function Object.defineProperty() .

Points to Ponder

  • There is no Return Value
  • The use of defineProperty

What can you do with it?

  • Making a property immutable
  • Setting Validators
  • Formating (through getters)
  • etc.

Class Decorators

Class Decorators basically modifies the constructor of a class. The Typescript way of doing this would be to extend the class being that is being decorated. By using class decorators, you will be able to modify/add new properties and methods to classes.

Decorator Signature

ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction;

Example Class Decorator in Action

The following Class decorator will modify the property speaker and add another property called extra .

function AwesomeMeetup<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor implements extra {
speaker: string = "Ragularuban";
extra = "Tadah!";
}
}
  //@AwesomeMeetup
class JSMeetup {
public speaker = "Ruban";
constructor() {
}
greet() {
return "Hi, I'm " + this.speaker;
}
}
  interface extra {
extra: string;
}
  const meetup = new JSMeetup() as JSMeetup & extra;
console.log(meetup.greet());
console.log(meetup.extra);

If you run the above code without the decorator, you will get the following output

Hi, I’m Ruban
undefined

If you run the code, with the decorator, the output would be

Hi, I’m Ragularuban
Tadah!

What is happening?

Class decorator takes one parameter, which is the class itself(constructor). We are essentially modifying the class by extending the class. I guess it would give you the idea.

Points to Ponder

  • Typings are missing (you have to provide typings. an alternative would be to create a factory which return the decorated class)
  • The extended class is called once the constructor of the original class is called.

What can you do with it?

  • Logging
  • Add additional behavior in the path of object creation
  • Everything and anything that you could think

Parameter Decorator

Now, you might be thinking parameter decorator is going to be used to change the parameter of a function. But wait… Parameter Decorator is a little different.

It is used to mark the parameters that need attention. You mark the parameters and then you make actions using a method decorator.

Decorator Signature

ParameterDecorator = (target: Object, propertyKey: string, parameterIndex: number) => void;

Example Parameter Decorator in Action

function logParameter(target: any, key: string, index: number) {
var metadataKey = `myMetaData`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
}
else {
target[metadataKey] = [index];
}
}
  function logMethod(target, key: string, descriptor: any): any {
var originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
      var metadataKey = `myMetaData`;
var indices = target[metadataKey];
console.log('indices', indices);
for (var i = 0; i < args.length; i++) {
        if (indices.indexOf(i) !== -1) {
console.log("Found a marked parameter at index" + i);
args[i] = "Abrakadabra";
}
}
var result = originalMethod.apply(this, args);
return result;
    }
return descriptor;
}
  class JSMeetup {
//@logMethod
public saySomething(something: string, @logParameter somethingElse: string): string {
return something + " : " + somethingElse;
}
}
  let meetup = new JSMeetup();
  console.log(meetup.saySomething("something", "Something Else"));

If you run the above code without the decorator, the output would be the following,

something : Something Else

If you run it with the decorator, the output would be the following.

something : Abrakadabra

What is happening?

Parameter Decorator takes 3 arguments(target, key,index). In the parameter decorator, we are essentially marking the parameters in an array in the target object (in the property myMetaData ). Nothing else.

Then, we are using the Method Decorator to read those marked parameters (in myMetaData) and then changing the marked parameters.

Points to Ponder

  • There is no return value
  • It’s usually combined with another decorator type

What can you do with it?

  • Dependency Injection is possible
  • Mark Parameters to apply Validators and formatters

You can write one Decorator Or you Just can Use existing ones 😛

Now that you have learned about different kind of decorators and how to use them, lettuce see what are the available decorators which are out there that you can use to straightaway.

Here are some cool Decorators out there

Feel free to share if you have found any cool decorator library.

I know, it’s been a long read. Thank you for taking the time to read this. If you think you can do a better explanation or if you think that I have gone wrong somewhere, please feel free to mention them in the comment section.

Start Coding; Have Fun 🙂


One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.