JavaScript Decorators for Declarative and Readable Code

Decorators in JavaScript are now in stage 2. They allow us to alter the definition of a class, method, or a property. There are already a few neat libraries which provide decorators and make our life easier by allowing us to write more declarative code with better performance characteristics.

In this blog post I’ll share a few decorators which I’m using on a daily basis. We’ll take a look at:

How to write more efficient React components with @autobind

How to cache results of computations using @memo

Improving separation of concerns and cohesion with aspect.js’s @beforeMethod and @afterMethod

Developing more decoupled code using Angular’s dependency injection with injection-js

Autobind

Autobind is a decorator which allows given method to be automatically bound to a class instance. For example:

This is a commonly used pattern for keeping a reference to an event handler so we can easily clean the subscription once the component gets destroyed. It works well, however, we can be achieved the same in a more elegant way:

An obvious idea for an optimization is to cache the result and directly return it once calculated instead of going through the same computation. A simple way to perform caching is to use a Map with keys the arguments of the calculateAge method and values the results the method produces. That’s exactly what memo-decorator does!

Notice the console.log statement that I added in the method. Here’s what the current behavior would be:

consthero=newSuperhero();hero.calculateAge(5);// return 5 and log 'Calculating'
hero.calculateAge(5);// return 5, does not log 'Calculating' since we get the result from the cache.
hero.calculateAge(15);// return 610 and log 'Calculating'
hero.calculateAge(15);// return 610, does not log 'Calculating' since we get the result from the cache.
hero.calculateAge(15);// return 610, does not log 'Calculating' since we get the result from the cache.

But what about methods which have multiple arguments?

In such cases we can provide a custom “resolver” which for given set of arguments returns the key to be used for caching:

As we can see from the snippet above, @memo receives a single, optional argument - a function. This function receives all the arguments passed to the decorated method. By default the implementation of the key resolver is the identity function:

constresolve=a=>a;

How to use?

You can use the @memo decorator from the memo-decorator package on npm.

aspect.js

So far we discussed different JavaScript decorators. Now we’ll briefly show an entire paradigm!

Let’s suppose we have a class called DataMapper which sends requests over the network to a RESTful API. This abstraction is supposed to create, update, and delete users and their payment information:

Now, for debugging purposes we want to add logging for all of these methods. Going over each individual method and adding a log statement will be quite annoying, and what if we want to drop the log statements for our production build?

A better approach would be to use Aspect-Oriented Programming (AOP), which can help us to resolve this issue by just:

How to use?

injection-js

Another extremely convenient use case for the JavaScript decorators is for dependency injection (DI)! Angular already takes advantage of this technique in order to provide a flexible way to wire up the dependencies in our application. I am sure I don’t have to convince you how useful the DI pattern is for helping us to write more testable and coherent code.

Did you know that you don’t necessary need Angular in order to use its dependency injection mechanism? That’s right, you can use the DI independently from the framework with the package injection-js!

Injection-js can be use with TypeScript, ES5, ES2016, ES2017, etc, however, it looks most natural with TypeScript because of its type annotations.

Once we invoke the get method of the injector instance, the injector will automatically figure out that Service depends on Http so it’ll create an instance of Http and pass it to the constructor of Service.

Conclusion

JavaScript decorators are on it’s way to the ECMAScript standard. There are a lot of libraries and frameworks out there taking advantage of them!

In this article we made a quick overview of only a few - @autobind, @memo, a few decorators from aspect.js, and injection-js.

We saw how we can develop more efficient React components and how to elegantly preserve a reference to event listeners by using @autobind. After that we saw how we can use the @memo decorator for applying memoization to pure methods. Our next stop was aspect.js package which implements the Aspect-Oriented Programming paradigm by using ES decorators! Finally, we took a look at injection-js which allows us to use the DI pattern for our Node, Vue, and React applications!