Create property and method decorators in TypeScript
In the previous part
of this article and video series you were introduced to TypeScript decorators.
You know that decorators come in different flavors such as class, property,
method, accessor, and parameter decorators. You also learned to create class
decorators. Continuing our learning further this part shows you how to create
property and method decorators.
Property decorators
A property decorator is added on top of a property. The function
designed to be a property decorator receives two parameters. The first parameter
is the prototype of the class under consideration. For static properties the
first parameter represents the constructor of the class. The second parameter is
the name of the property itself.
Consider the following property decorator that has been added on top of the
Employee class properties.
class Employee {
@Required
employeeID: number;
@Required
fullName: string;
constructor() {
}
showDetails(): void {
}
}
As you can see, employeeID and fullName properties are decorated with
@Required property decorator. The @Required decorator registers the property
under consideration as a mandatory property, a property whose value must be
assigned. The other parts of the code such as showDetails() method can check
whether all the properties that have @Required are assigned values or not. So,
basically you want to create something similar to ASP.NET Core MVC's
ModelState.IsValid() technique.
Let's see how this can be accomplished.
Before we create the @Required property decorator, we need to device a
mechanism that will allow us to register properties for "required" validation.
We will create a class called RequiredPropertyValidator that does this job. Take
a look at the following code:
class RequiredPropertyValidator {
static properties : string[]
= new Array();
static IsValid(emp: Employee): boolean {
let flag: boolean = true;
this.properties.forEach(function (prop) {
if (typeof emp[prop] === 'undefined') {
flag = false;
}
});
return flag;
}
}
The RequiredPropertyValidator class has a static property called properties.
The properties property is an array and holds names of all the properties that
require the validation.
The IsValid() static method accepts an Employee object as its parameter.
Inside, the code iterates through the properties array and checks whether that
property on the emp object is undefined or not. If a property is undefined that
means it is not assigned a value (of course, this is just a basic check. You can
modify this code to consider all the possibilities.). In that case flag variable
is set to false and IsValid() will also return false.
The @Required decorator registers a property with RequiredPropertyValidator
by pushing it in the properties array. This is how @Required decorator looks
like :
function Required(target: any,
propertyKey: string) {
RequiredPropertyValidator.properties.
push(propertyKey);
}
As discussed earlier, a property decorator function receives two parameters -
prototype and property name. In this case we use only the propertyKey parameter
to register that property with RequiredPropertyValidator.
Now that @Required and RequiredPropertyValidator are ready, we can complete
the showDetails() method:
showDetails(): void {
if (RequiredPropertyValidator.
IsValid(this)) {
document.getElementById("employeeID").
innerHTML = this.employeeID.toString();
document.getElementById("fullName").
innerHTML = this.fullName;
}
else {
document.getElementById("msg").
innerHTML = "Validation errors!!!";
}
}
Notice the code shown in bold letters. The code calls the IsValid() method by
passing "this" object to it. So, employeeID and fullName will be checked during
the validation. If any of the two is undefined then an error message is
displayed on the page. If both properties contain some value then those values
are displayed.
Modify the DecoratorDemo() function (we wrote it in the previous part of this
series) as shown below:
function DecoratorDemo() {
let emp = new Employee();
emp.employeeID = 1;
emp.fullName = 'Nancy Davolio';
emp.showDetails();
}
Here, the code creates a new object of Employee and assigns values to the
employeeID and fullName properties. The call to showDetails() produces the
following results:
Now, comment out the property assignments. You should get the following
result:
As you can see, now you get the validation error message because IsValid()
returns false.
Method decorators
Method decorators are added to class methods. The method decorator function
receives three parameters. The first two parameter is the prototype of the
object (or constructor if the method is static). The second parameter is the
method name. And the third parameter is a Property Descriptor object. A property
descriptor is an object that provides information such as property value, getter
and setter methods, whether the property is configurable, enumerable, and
writable. It implements PropertyDescriptor interface. You can read more about
property descriptors
here.
Now that you know about method decorators, let's create one for showDetails()
method of the Employee class :
@ConfirmBeforeExecute
showDetails(): void {
// code here
}
As you can see, the showDetails() method is decorated with @ConfirmBeforeExecute
decorator. The @ConfirmBeforeExecute decorator adds confirmation functionality
to the showDetails(). This means when showDetails() is called it will first ask
consent from the user to run the method.
If the user clicks on OK the showDetails() runs and displays employee details
like this :
otherwise the call is cancelled and a message is displayed as shown below:
The @ConfirmBeforeExecute decorator function is shown below :
function ConfirmBeforeExecute(target: any,
propertyKey: string,
descriptor: PropertyDescriptor) {
let func = descriptor.value;
descriptor.value = function () {
let thisValue = this;
let args = arguments;
let flag = confirm("Do you want
to run showDetails() method?");
if (flag) {
func.apply(thisValue, args);
document.getElementById("msg").
innerHTML = "Method executed successfully !!!";
}
else {
document.getElementById("msg").
innerHTML = "Method cancelled by user !!!";
}
}
}
As you can see, the ConfirmBeforeExecute() function takes three parameters
discussed earlier.
Inside, the code stores the original value of the method into func variable.
In this case @ConfirmBeforeExecute is added to showDetails(). So, the value
property will be the showDetails() method.
Then the code replaces the original value with a new function. The new
function shows a Confirm() dialog to the user to seek confirmation. If user
clicks OK the flag will be true and the showDetails() get executed. To execute
showDetails() we use
apply() method and pass this and method arguments. In this case showDetails()
doesn't take any parameters.
A success message is also rendered in the msg <div> element. If user clicks
on the Cancel button, showDetails() doesn't execute and an error message is
displayed.
I hope you got some idea about class, property, and method decorators. You can also watch the companion video
here.
In the next part we will explore how generics is used in TypeScript.
That's it for now! Keep coding!!