Learn ASP.NET Core 3.1 with mini project : MVC, Razor Pages, Web API, Entity Framework Core, and Blazor. Hands-on online training courses. Click here to know more.

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!!


Bipin Joshi is an independent software consultant, trainer, author, yoga mentor, and meditation teacher. He has been programming, meditating, and teaching for 24+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Facebook  Twitter  LinkedIn  YouTube

Posted On : 25 May 2020


Tags : ASP.NET ASP.NET Core MVC .NET Framework C# Visual Studio


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates