Nov 18, 2021 . 5 min read
See the ways in which it provides us to add extra functionality to our class declarations and members
Photo by Paul Esch-Laurent on Unsplash
This article assumes some knowledge in JavaScript/TypeScript as well as Linux-based operating system. For window users, please find similar commands that correspond to their Linux counterparts.
Decorators are the methods by which we are able to wrap code with another. Essentially like a wrapper. Decorators are a useful design pattern that allows certain specific behavior to be injected into an object/function, either statically or dynamically.
Decorators can be attached to a class declaration, method, accessor, property, or parameter.
Popular frameworks like Angular and Vue use decorators as well!
You can view the full code here
We will set up the basic backbone of our TypeScript Node.js project
mkdir decorators # creates a folder called decorators
cd decorators # enter the decorators folder
npm init -y # creates a package.json for our usage
Let's install our dependencies. Assuming we don't have TypeScript installed globally, we can install it locally in our project. We will need @types/node package to provide us with the Node.js type definitions and we need ts-node to help us compile our TypeScript code and run
yarn add typescript ts-node @types/node -D
Now let's set up our package.json
Since TypeScript has this feature as experimental, we will have to configure our tsconfig.json file. However, before that, let's create our tsconfig.json file
touch tsconfig.json
Add these to tsconfig.json
A decorator factory is simply a function that returns the expression that will be called by the decorator at runtime. We are essentially returning our decorator function. If you are familiar with OOP languages such as Java, it is a similar concept to Factory Method, however, instead of returning an Object, we are returning the decorator function.
Decorator Composition is an important concept as it allows us to use multiple decorators on a single class member or declarations. The evaluation of these compositions is similar to the function composition that we will see in our mathematics class (i.e (f ∘ g)(x) is equivalent to f(g(x))).
From the code example below, we can observe that the expression for each decorator will be evaluated from top to bottom, however, the results are called from bottom to top.
Now we create a file for this example and insert the following code.
touch decorator-composition.ts # creates a ts file
Now run npm run decorator-composition. The expected results are
Outer: expression
Inner: expression
Inner: result
Outer: result
This shows that the outer expression is evaluated first and then the inner results (decorator function) will be evaluated first.
Class Decorator is very similar to inheriting from another class. So what's the advantage of using decorator over inheritance then?
Inheritance is often considered a design smell because excessive use of inheritance often results in an exploding class hierarchy.
Also, some languages only support single inheritance. Using decorator, multiple decorators can be stacked on top of each other, each adding their own functionality, and decorator patterns follow composition pattern which is favored over inheritance pattern. See why.
The constructor of the decorated class will be the argument of the class decorator. You can do things like overriding some properties, adding some properties, method overriding, etc.
Here we will show how to add an extra property into our class itself. Note that you will realize that TypeScript will sound of an error that the property is not found (in this cardId is not found). This is a known issue in TypesScript. Here we will simply use @ts-ignore to help us compile the code.
touch class-decorator.ts
Running npm run class-decorator will have the following expected result
Dennis
some random number
Property decorators have multiple functionalities, in this example, we will be showing logging functionality. The decorator will take in two parameters in this order
Here we will log the new value whenever the property pin is being changed.
touch property-decorator.ts
Running npm run property-decorator will have the following expected result
new pin value: 123456
pin value: 123456
new pin value: 654321
The first value was due to the constructor initializing which is setting the pin value, hence being called in the set() method.
The second value is when we access bankPropertyDecorator.pin thus it is called in the get() method.
The third value is when we reassign bankPropertyDecorator.pin thus it is called in the set() method.
Method decorator can be used to observe, modify or even replace a method definition. The decorator will take in three parameters in this order
Here we will use an interesting example of using a method decorator as an automatic error guard for bank withdrawals.
touch method-decorator.ts
Running npm run method-decorator will have the following expected result
accountA: Not enough money
200
800
We can see that there is no withdrawal from account A due to insufficient amount to meet the minimum amount after withdrawal.
Accessor decorator is very similar to the method decorator since getters and setters are themselves methods.
TypeScript disallows decorating both the get and set accessor for a single member. Instead, all decorators for the member must be applied to the first accessor specified in document order. This is because decorators apply to a Property Descriptor, which combines both the get and set accessor, not each declaration separately. -TypeScript Handbook
Similarly, the decorator will take in three parameters in this order
Here we will show how our accessors can be used to modify the output by setting the output as uppercase.
touch accessor-decorator.ts
Running npm run accessor-decorator will have the following expected result
KENNY
We can see that despite the name being Kenny, the output is all capitalized.
Parameter decorators are often used for adding Metadata. The TypeScript handbook provides an excellent usage of it. Normally, it is often paired with the Method Decorator.
All in all, you can leverage decorators in your code and create multiple patterns and usage with them.
Decorators also allow and promote code reuse and make your code more extensible.
Hope that you find this article useful.
View Medium article here