Start Implementing Your Own Typescript Class 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).
Typescript Class Decorators Definition: The class decorator is applied to the constructor of the class and can be used to observe, modify, or replace a class definition.(reference)
What is going on behind the scene?
Your class decorator is actually a simple function that is called as a function at runtime and it gets one argument and that is the constructor function of the class. If the class decorator returns something it is going to be used as the constructor of the class. These things might sound confusing but it’s going to be clear as day after taking a look at some pieces of code.
Setup
In order to run the Typescript code, we need to compile them using the Typescript compiler.
We need a tsconfig.json file :
https://medium.com/media/39e61b1249de30de02c2830a5244d69b/hrefWe 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.(reference)
Define a Class Decorator
First class decorator we want to create is just going to log a message on the console and tell us that it is running.
https://medium.com/media/86a7aa80e3ee4ce7a713f02af9a16a81/href
Now if I compile and run this code I will see this on the console:
Here the class decorator was a function that had constructor of the class as its argument and logs something on console easy-peasy. We can see that before running the constructor of the class it runs the class decorator.
Let’s see an example that we can pass our constructor in the decorator:
https://medium.com/media/56031b8ba6d683b3215b01a8a67006d5/hrefIn order to make some changes to constructor of the class from the class decorator we have to return a class that extends the base class.
The Typescript compiler forces us to do this(return a class that extends the base class).
Why compiler is forcing us do this? Because otherwise the type we are returning is not compatible with the base class.
If what is happening is still vague you should recall that classes are actually functions(You may find so many helpful comments on that here).
https://medium.com/media/8149596a93aed43fd64e86da5feb9660/hrefNow that we know this, consider the class decorator as a higher order function. It is somehow similar to the implementation of method decorator.
What if we want to use a class decorator on all classes? That is possible by using Typescript Generics. Take a look at this example:
https://medium.com/media/1bca8141f9a41e450fe6431a1746c53f/hrefWe can use this class decorator on all classes.
<T extends { new (...args: any[]): {} }>
This is the definition of generic and if you are wondering why it extends { new (...args: any[]): {} you should know that we expect a constructor function and this is how we can tell the compiler to check that.
Here T is the generic class and we are able to return new constructor by extending it in line 4.
In line 5 and 6 we added two timestamps members to the class. Now each class that wrapped by this decorator has these two timestamps created_at and updated_at .
Let’s see the result on console:
As we can see class has two additional members updated_at and created_at
Can we access these two additional properties?
Calling Additional Properties that added by Decorator
The Decorator does not change the Typescript Type
The only way to access that property is to tell the compiler to ignore that line. If you consider timestamps example, we usually want to pass them to an ORM or ODM to save a record or document in the database, so there should be no problem even if we do not call them directly. These properties are going to be passed in the object.
Another really useful decorator that is mentioned in official TS Docs is sealed decorator.
Sealed Decorator
When we seal a class we want to prevent more functionalities and properties to be added or removed. Let’s see that in action:
https://medium.com/media/615cd03dd8d6e9f73a2173da533cf301/href
We sealed the constructor and it’s prototype. If we try to change the constructor this way(We had to ignore the compiler to be able to do this):
https://medium.com/media/5f85915def8a4c38978cfd6a3cb7c8e4/href
That will result in a TypeError:
Result of changing a class prototype
If we do not use @sealed decorator then this is going to be the result:
Calling getHeight without sealed decorator
Conclusion
Class decorators are simply a function that runs at runtime and it can change behavior of a class. Using Class Decorators and wrapping other classes can be a little bit tricky in some cases if you want to add some functionalities to them since the decorator does not change the typescript type.
Decorators are widely used in Angular and NestJS since both of these frameworks use OOP paradigm and you can take a look at their documents and the implementation of decorators.
Top comments (0)