PART II: Property decorators & Class decorators
I will assume that you have already read the Part I of these series and you know the answer to these questions.
In this post we will learn about the two new decorator types:
PropertyDecorator
andClassDecorator
.Let’s start with
PropertyDecorator
.
Property decorator
- As we already know, the signature of a PropertyDecorator looks as follows.
|
- We can use a property decorator named logProperty as follows:
|
- When compiled into JavaScript the __decorate method (which we explained in PART I) is invoked here but this time it is missing the last parameter (a property descriptor obtained via Object.getOwnPropertyDescriptor).
|
This is the reason why the property decorator takes 2 (prototype and key) arguments as opposed to 3 (prototype, key and property descriptor) like in the case of the method decorator.
Another thing that we should notice is that this time the TypeScript compiler is not using the return of __decorate to override the original property like it
with the method decorator.
|
This is the reason why property decorators don’t return.
Now that we know that a property decorator takes the prototype of the class being decorated and the name of the property being decorated as arguments and don’t return, let’s implement the
logProperty
decorator.
|
The decorator above declares a variable named _val and sets its value to the value of the property being decorated (since this refers to the class prototype here and key is the name of the property).
Then, the functions getter (used to get the value of the property) and setter (used to set the value of the property) are declared. Both functions will have permanent access to _val thanks to the closures created when each of these functions are declared. Here is where we will add some extra behaviour to the property. In this case we have added a line to log in console the changes in the property value.
Later, the operator delete is used to delete the original property from the class prototype.
Note: The delete operator throws in strict mode if the property is an own non-configurable property (returns false in non-strict).
If the property is successfully deleted, The Object.defineProperty()
method is used to create a new property using the original property’s name but this time the property uses the previously declared getter and setter
functions.
- Now that the decorator is ready it will log in console the changes to the property every time we set or get its value.
|
Class decorator
As we already know, the signature of a ClassDecorator
looks as follows.
|
We can use a class decorator named logClass
as follows:
|
When compiled into JavaScript the __decorate
function (which we explained in PART I) is invoked here but this time it is missing the last 2 parameters.
|
We should notice that the compiler is passing Person and not Person.prototype to
__decorate
. This is the reason why the class decorator takes 1 (the class constructor) argument as opposed to 3 (prototype, key and property descriptor) like in the case of the method decorator.Another thing that we can notice is that this time the TypeScript compiler is using the return of
__decorate
to override the original constructor
|
This is the reason why class decorators must return a constructor function.
Now that we know that a class decorator takes the constructor of the class being decorated as its only argument and must return a new constructor, let’s implement the logClass decorator.
|
The decorator above declares a variable named original and sets its value to the constructor of the class being decorated.
Then, a utility function named construct is declared. This function allow us to create instances of a class.
We then create a variable named f that will be used as the new constructor. This function invokes the original constructor and will also log in console the name of the class being instantiated. Here is where we will add some extra behaviour to the original constructor.
The prototype of the original constructor is copied to the prototype of f to ensure that the instanceof operator works as expected when we create a new instance of Person.
Once the new constructor is ready we just need to return it to finish the class decorator implementation.
Now that the decorator is ready it will log in console the name of a class every time it is instantiated.
|
Conclusion
We now understand in-depth 3 out of the 4 available types of decorators. We know how to implement them and how they work internally.
In the next post we will learn about the last type of decorator (the parameter decorator) and how to create a universal decorator that we can apply to classes, properties, methods and parameters.