Start Implementing your own Typescript Method Decorators
What is a Decorator?
It is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors (reference).
When we are using Typescript Method Decorators, they are higher-order functions that help us change method behavior or do something with the arguments.
Typescript method decorator definition: method decorator can be used to observe, modify, or replace a method definition (reference)
Now, let’s see how we can define a simple method decorator.
Setup
In order to run the Typescript code, we need to compile them using the Typescript compiler.
We need a tsconfig.json
file :
We have to enable the experimentalDecorators
. Also, the target should not be less than ES5
.
If you do not want to use a tsconfig
file you can pass these options directly:
tsc --experimentalDecorators // If you installed tsc globaly
npx tsc --experimentalDecorators // If you installed tsc in your current directory
Now by running tsc
in the current directory, the typescript files will compile to javascript files and we can run them using Node.
Define a method decorator
The general structure of a method decorator in typescript is like this:
Passing arguments to the decorator
We can pass a variable to the decorator and we have access to it inside the decorator. we passed an object in line 13 and it is going to be the argument in line 1.
The return function has 3 arguments.
* target: contains our target(the function, class, property that we want to do something with it)
* key: In method decorator, it is the name of the method.
* descriptor: It is a property descriptor and it gives us the child function(method) in descriptor.value
and we can change the behaviors of the method.
If you are not familiar with property descriptor this article will help you understand it but in plain language, it is something that helps us to define properties for an object and we have some options or we can set constraints on that property.
In the first example, we want to do something really simple we want to log the name of the function each time someone calls it.
Example1: Function name logger
All we did in this decorator is logging the key in line 7
.
Important: You cannot use a decorator outside a class(Decorators have meaning when we are using classes)
Now if I run this file using this command: npx tsc; node loggerDecorator.js
I can see the name of the function in my console: Sum
.
Example 2: Check Duplicate User
Now let’s write something more real and see how we can use the descriptor. We want to write a decorator that checks if the user email is duplicated or not. We can implement something like this inside our method but if we want to use it more than once in our code, a decorator to check this will be helpful and have to know that this is just an imaginary example to show you how it works(There are zillion ways to implement this better than example :) don’t get too bogged down).
Let’s see what is happening here line by line:
line 1: We have a const
userEmails
which contains a sample array of user emails. This is supposed to come from the database.
line 8: If the user is not duplicated, we want to run the child function so we need to store the original method in a variable.
line 9: This is how we can change the function. we can access the arguments of the child function here and since we may have multiple arguments we need to extract the arguments like an array(by using …
).
line 11: we are checking if the email is duplicated or not. we expect the first argument to be the user object so to access it we have to use args[0]
and we check that if the email exists or not using indexOf
which is a method that returns -1
when the element does not exist in the array. If the email is duplicated we are going to return null
so the method will not be called. If the user is not duplicated we have to execute the method so I returned childFunction.apply(this, args)
and as a result, if the user is not duplicated, the method will execute with its arguments.
line 21: This is the user service class which has a method named createUser
and we used our method decorator to check if the user is duplicated or not. If the user is duplicated we should not see something in the console and if the user is unique, the method should be called and as a result, we should see the log on the console.
line 28: We instantiated from the UserService
class and after that, we are using the createUser
method twice in lines 29–30. one of the emails is used before so we expect that the method createUser
to be called only one time for the user with email: john.smith@gmail.com
This is the result on my console:
Conclusion
Using method decorator is not that hard and if we are using typescript and object-oriented programming we can use decorators to change methods behavior. we have access to arguments of method and the arguments of the decorator and we can do something based on arguments.