Decorator Pattern
When we are cold, we put on a shirt. If that’s not enough, we put a coat on top. Decorators work similarly. In this article, we discuss the principles of the decorator design pattern and talk about th
The basics of decorators
The Decorator Pattern is one of the well-known design patterns described by the “gang of four”. The fundamental purpose of decorators is to attach new features and behaviors to already defined classes and methods. If we consider the above to be the core of decorators, it is easy to implement our decorator:
Thanks to the frozen
function, we now can get an object which properties can’t be changed. The above happens thanks to the usage of the Object.freeze function. We’ve made it easily reusable, and therefore, we can attach it to an object of any class.
Furthermore, our decorators can be stacked up on top of each other. It might sometimes be tricky, though.
The above example might not work as you expect, because when we attempt to make the yogurt to be eatable, it is frozen already. We need to revert the order of our decorators.
The code above might look a bit like using Higher-Order Components with React. You could actually treat them as hooks and they are sometimes seen as such.
We can imagine that this pattern would come in handy quite often. Unfortunately, the above syntax is not very elegant. Thankfully, the efforts have been made to standardize decorators both in JavaScript and TypeScript.
Decorators in TypeScript
First, let’s look into decorators in TypeScript, because they are a built-in feature. The only thing we need to do is to turn on the experimentalDecorators
flag in our tsconfig.json file.
As you can see above, the decorators are still an experimental feature in TypeScript, and they might change in the future.
The Decorator is a special syntax that provides us with a way to attach additional logic to classes, methods, accessors, parameters, and properties. They are used heavily in frameworks like Angular and NestJS.
Class decorator
We can declare a class decorator by adding it just before the class declaration. Its argument is the constructor of our class.
Let’s define a decorator that logs out the name of every created instance.
The issue with the code is that ourconsole.log
is called only once when our class is initialized. Thankfully, we can replace the original constructor by returning a value from our class decorator.
In the above code, we log out the name of the class every time we call the constructor.
new Yogurt('strawberry');
new Yogurt('cherry');
Method decorators
We add a method decorator just before the declaration of a method. We can use it to track, alter, or replace a method definition.
This is a great moment to use the decorator factory approach. It involves creating a function that returns a decorator. We can find it very useful when wanting to pass some additional properties to the decorator.
The method decorator has three arguments:
the constructor function of the class (for a static property), or the prototype of the class (for instance property),
name of the property,
the property descriptor.
Let’s create a method decorator that removes provided properties from the return value. We might find it useful, for example, to strip out a hash of the password of a user, when returning his data.
In the code above, we save the reference to the original function using the descriptor.value property. We use the async/await syntax because we want to use the excludeProperties
function to decorate a method that returns a promise.
Thanks to our decorator, the getUserById
function returns a user but removes the password from the return value.
Property decorators
We can add property decorators just before declaring a property. It has two arguments:
the constructor function of the class (for a static property), or the prototype of the class (for instance property),
name of the property.
As you can see above, the arguments do not include property descriptors. We can’t really do much with them alone, but you might find them useful when exploring the reflect-metadata library.
Accessor decorators
The accessor decorators are similar to the method decorators. The catch to them is that we can’t decorate both get and set accessors of a single property. We need to apply a decorator to the first accessor in our class.
Parameter decorators
We can add a parameter decorator before the declaration of a parameter inside of a function. It has three arguments:
the constructor function of the class (for a static property), or the prototype of the class (for instance property),
name of the property,
the index of the parameter
Or at least the documentation states the above is the case. There seems to be some confusion regarding the second argument.
Similarly to the property decorators, the parameter decorators can just be used to observe that a parameter was declared and do some side-effects. You might find it useful with the reflect-metadata library.
Summary
The decorator pattern proves to be useful when we want to assign some additional behavior.
In this article, we’ve gone through all of the types of decorators that we can use.
They are an elegant and reusable solution and allow us to delegate some of the logic from outside of our classes, making the Single Responsibility Principle easier to keep.
Resources
Last updated