JavaScript and TypeScript Hidden Gems: Decorators

Although still an experimental feature, you can already play around with decorators and start seeing what you’ll be able to do with them.

Fernando Doglio
Bits and Pieces
Published in
10 min readOct 19, 2020

--

Decorators are not new to the programming world, in fact other languages have been using them for years now. But in our case, both for JavaScript and TypeScript developers alike, they’ve been a feature we’ve been looking at like a child outside of a toy store the week before Christmas.

The main benefit of using decorators is that they allow you to enhance your code using a declarative syntax, which is quite easier to read. With but a single line you can suddenly cache your function calls, or turn your class method into a REST endpoint, no extra code required. They feel like magic when used and you feel like you’re working with a real powerful tool.

The truth of course, is that these decorators have a function associated to them behind the curtains and we’ll see how you can use that logic to create some interesting behavior in your code.

A disclaimer for the article: both for JavaScript and TypeScript, decorators are an experimental feature, but for the first one they’re still on stage 2 of the proposal , which means they’re not yet implemented. This is why the code samples will be done with TypeScript, but know that the concept is the same for the current version of the proposal for JS.

Tip: Share your reusable components between projects using Bit

Bit (GitHub) makes it simple to share, document, and organize independent components from any project.

Use it to maximize code reuse, collaborate on independent components, and build apps that scale.

Bit supports Node, TypeScript, React, Vue, Angular, and more.

Example: exploring reusable React components shared on Bit.dev

Using decorators in TypeScript

Since this is an experimental feature, you’ll have to either provide a special flag for the CLI in order to be able to use this feature of the language or add a new key the tsconfig.json file.

The CLI line should look like this:

$ tsc --target ES5 --experimentalDecorators

And in the case of the configuration file:

Using decorators in your code

First things first, the current implementation of decorators allows for you to decorate everything inside a class, including of course, the class itself. This means, you can decorate:

  • The class, like I mentioned, by extending it and returning a potentially modified version of it.
  • The class methods, you can enhance the behavior of them by adding pre and post conditions to their logic or even change them altogether if you wanted to.
  • The method’s parameters. Yes, you can decorate a single method parameter if it was required.
  • The class accessors. Do note that if you have both a getter and a setter for a single attribute, you won’t be able to decorate one of them. If you decorate one, the code will affect both (we’ll see why in a minute).
  • The class attributes. Of course, you can also decorate single attributes inside a class.

For each one, decorators will be applied during declaration time, not execution. This means that for a method decorator, the code will be executed when you declare the method, not when you call it. This is important to understand the type of logic you can add to them, so keep that in mind.

Let’s quickly go one by one and explain how you’d write a decorator for them and why would you do it.

Class decorator

A common mistake with class decorators is to confuse them with a simplified version of class inheritance. This in my opinion, is TypeScript’s own documentation fault, since they provide the following incorrect example:

And that example works, once you reach the last line, as part of the console.log output, you’ll see the added newProperty property. However, if you tried to use that property anywhere in your code, as you would any other property of that class, you’d get the following error:

error TS2339: Property 'newProperty' does not exist on type 'Greeter'.

And that would be completely correct, because you’re not supposed to mutate the object you’re decorating here. You’re supposed to add metadata to it. You’re supposed to add behavior around it, but not change its nature.

This example takes the same code, and changes the logic on the decorator, now you’re just adding logic around the class declaration. You’re logging a message notifying the moment the declaration took place. This works.

Probably the problem causing the confusion here is the chosen name: decorator . Which makes you think of the decorator pattern, which is in fact, a way of extending an existing behavior and mutating it. However, JAVA got it right here (and trust me, the fact I’m saying JAVA got something right is a huge deal here), they called these constructs “annotations”. Which is more inline with their actual intent.

When you add notes to your book, you don’t scratch a full sentence and then replace it with your own, you just add a note to provide extra meaning (i.e metadata) or to reference something else that might be related (i.e logic around it) but the original text remains intact and the book can be read either without your notes or with them. This last bit is key:

The rest of your logic needs to work with or without the decorators.

Once could say that this is a clear case of “just because you can, doesn’t mean you should”. Especially for the following decorators which, by nature, allow for further modifications to the targets they decorate.

Method and accessors decorators

I decided to cover both of these at the same time, because their function signature is identical. Both type of decorators receive the same three parameters:

  • The target, which is the class the method or accessor belongs to.
  • The property key, which is a string with the name of the method or accessor.
  • The property descriptor (of type PropertyDescriptor, you can read the JS documentation for what that looks like here) which will be slighly different depending on whether it’s a method or an accessor.

And that is it, through the last parameter you can access the actual logic in order to execute it (if you so required).

Let’s quickly cover a crude example of a decorator that will cache method calls in order to improve performance:

First look at the class definition, we’re not doing rocket science here, it’s a simple example of an adder method. We could’ve done this way more complex but imagine that adding two numbers is a potentially expensive operation. Thanks to the @numericMomoizer decorator, I’m telling JS to cache the results of calling this method. Remember, the decorator will be called once, when the method definition is read, so the cache will only be instantiated once, but I’m creating a closure that gets returned as part of the returned value of the decorator (the decorator is returning the property descriptor, which replaces the original one, with the new code inside the value property).

The output from this function should be something in the lines of:

30
40
Returning cached version of the results...
30
Returning cached version of the results...
30

The key takeaway from this should be that as you can see, you can modify the target object without actually modifying its structure. Here the original code remains the same, I’m only adding logic around it. You can, however, completely replace the method logic and add something entirely different, but that would be the equivalent of modifying the classes structure. You can do it, but you shouldn’t.

In the case of accessors, the same logic applies, but instead of having the value property, you get two properties, get and set. You get both every time,which is why you don’t need to add the decorator to both, getters and setters. If you plan on affecting both, you can simply add them once and have your decorator code apply the changes there.

Property decorators

For property decorators, we do not get a property descriptor as part of the attributes received on our function, instead we only get the target object (i.e our class) and the property name. However, we can still do some interesting things, such as the following example which showcases a couple of things:

  1. Decorators don’t necesarily have to be functions, they can be class methods as well.
  2. You can create a decorator factory, which is a function (or method) that returns a decorator. You’ll see why they’re important in a second.
  3. You can store metadata outside the decorator’s code in order to enhance the experience.

Again, looking at the MyClass class, you’ll see our code is actually very simple, but we’re enhancing the value we get from a simple string property by storing metadata about it somewhere else and then using it when required.

The output from this code is, of course:

Hi there how are you?

But let’s unpack the MetadataFormatter class, which has two static methods. The first one, formatDeco is the actual decorator, or should I say decorator factory, since its sole purpose is to return the real decorator. The utility behind factories is that you can reuse the decorator’s code with different parameters. For our example, we’re passing the actual format to the factory, which allows us to reuse the exact same call on another property having a different format. Otherwise, if we had harcoded the format inside our decorator, we would need one decorator for each format, essentially repeating the same code over and over again.

The second method, getFormat simply retrieves the stored value for a specific object, and a specific property inside that object.

Using decorators to store metadata about properties (and other decorated targets as well) is a great example of why decorators are so useful. Here I’m able to add so much value to an otherwise uni-dimensional variable because this syntax is letting abstract the complexity into a single line of code.

Parameter decorators

Finally, parameter decorators allow us to monitor parameters passed to our methods. The distinction here is that we can’t really change anything through this decorator since its value is ignored.

An interesting use case for this type of decorator is to flag parameters with an expected behavior. The downside, is that you then need another decorator to enforce it. Let me show you:

Let’s first look at the TestClass definition: on its add method we’re using 2 decorators. We’re flagging the second parameter as required which means we’ll make sure it’s not undefined during execution. The issue with only using this, is that since its return value is ignored, we can’t overwrite the original method’s behavior to take into account that flag.

Enter the second decorator: validate .

The second decorator is simply overwriting the original method, so it first will check the values received, compare it against the flagged attributes and if one of them is a match (in other words, it received an undefined when it shouldn’t have) then it throws an exception.

Both decorators again make use of an external structure to store and retrieve these flags.

Instead of checking for undefined you could rewrite the required decorator, for example, to be a factory which allows you to define a valid range of numbers. This way you can then perform more complex validations. There are many use cases for this decorator, just remember you’ll have to pair it with a method-level decorator in order to actually use it.

Decorator composition

One thing I haven’t covered so far about decorators, is that you can use more than one at any given time on the same target. This is also known as composition, which is not strange, considering these are functions, you’re just composing functions together.

Here is a very simple example:

Can you guess the output you’ll get from that code? Here, I’ll show it to you:

One decorator evaluated
Another decorator evaluated
Another decorator called!
One decorator called!
Method called!

Essentially you can see how there are two phases: evaluation and execution. During evaluation, your decorators will be called in the order they were defined (i.e line 18 first, then line 19), but since these are factories, they will then be evaluated as the functions they are: oneDecorator(anotherDecorator()) meaning the first one to be called will be the one returned on line 19, and then the one on line 18.

If you’re not using factories, but rather simpler decorators like in the next example, there will only be one phase:

Here there is no evaluation phase, or essentially, there is one, but you’re left out of it because you’re not calling functions anymore. The output for this code will be the equivalent of the execution phase:

Decorator 2 called!
Decorator 1 called!
Method called!

Keep that difference in mind when you’re setting up the composition, the order of execution might affect your end result.

Final words

Decorators are fun! And they allow you to slowly enter into a field I really like which is metaprogramming. Sadly, JavaScript (or rather ECMAScript) is still not sure about how to implement this feature and because of that (I think) TypeScript is still flagging decorators as experimental.

That being said, this is still a very interesting feature and used right, it can allow you to add very interesting behavior on top of existing code and even combine it with normal logic to provide a clean and declarative syntax, which is something we should all strive for.

Have you used decorators yet? What are your thoughts on it? Will they make it into the language eventually or be kept as a hidden feature behind an experimental flag? Leave a comment down below and share your thoughts!

Learn More

--

--

I write about technology, freelancing and more. Check out my FREE newsletter if you’re into Software Development: https://fernandodoglio.substack.com/