”;
Decorators in TypeScript are special kinds of declarations that can be attached to the class declaration, property, accessor, method, and parameters. It is used for the separate modification of the class without modifying the original source code. This makes them a powerful tool in the domain of object-oriented programming, allowing you to write cleaner, more organized code that adheres to the DRY (Don”t Repeat Yourself) principle.
Using Decorators in TypeScript
You are required to enable the ”experimentalDecorators” compiler option to enable the experimental support for decorators in your TypeScript project.
There are 2 ways to enable ”experimentalDecorators” compiler option in TypeScript. You can use any option.
- Execute the below command in the terminal in the project directory.
tsc –target ES5 –experimentalDecorators - Or, you can update the tsconfig.js file and add “experimentalDecorators”: true attribute in compilerOptions object.
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }
Decorator Syntax
You can follow the syntax below to use decorators in TypeScript.
@DecoratorName
In the above syntax, ”DecoratorName” is a function name that is prefixed with the ”@” symbol. The expression must evaluate the ”DecorateName” function at the run time.
Decorator Factories
Whenever you need to customize how the decorator function is applied to declarations, they can use the decorator factories. A decorator factory function returns the expression which will be evaluated at the run time.
Follow the syntax below to use the decorator factory function.
function decoratorName(args: string) { // Decorator factory returns the function expression return function (target) { // This is the decorator which will be evaluated at the run time. }; }
In the above syntax, ”args” is an argument passed to the decorator function, and ”target” is a prototype of the class.
Decorator Composition
You can use multiple decorators with particular declarations. Multiple decorators either can be used in a single line or multiline as shown below.
In a single line:
@f @g x
OR, In multi lines:
@f @g x
In the above syntax, ”f” and ”g” decorators are used with a single declaration ”x”.
Why Use Decorators?
Let”s take a simple example to understand the use cases of decorators.
// Defining a class class Student { // Declaring the properties of the class constructor(private name: string, private rollNo: number) { } // Defining the methods of the class sayHello() { console.log(`Hello, my name is ${this.name}.`); } printrollNo() { console.log(`My RollNo is ${this.rollNo}.`); } } // Creating an object of the class const user = new Student("John", 20); // Accessing the properties of the class user.sayHello(); user.printrollNo();
On compiling, it will generate the following JavaScript code:
// Defining a class class Student { // Declaring the properties of the class constructor(name, rollNo) { this.name = name; this.rollNo = rollNo; } // Defining the methods of the class sayHello() { console.log(`Hello, my name is ${this.name}.`); } printrollNo() { console.log(`My RollNo is ${this.rollNo}.`); } } // Creating an object of the class const user = new Student("John", 20); // Accessing the properties of the class user.sayHello(); user.printrollNo();
It produces the following output:
Hello, my name is John. My RollNo is 20.
Now, what if we want to log the function when the execution of the function starts and ends? We need to add logs at the start and end of each function as shown in the below code.
// Defining a class class Student { // Declaring the properties of the class constructor(private name: string, private rollNo: number) { } // Defining the methods of the class sayHello() { console.log("Start: sayHello"); console.log(`Hello, my name is ${this.name}.`); console.log("End: sayHello"); } printrollNo() { console.log("Start: printrollNo"); console.log(`My RollNo is ${this.rollNo}.`); console.log("End: printrollNo"); } } // Creating an object of the class const user = new Student("John", 20); // Accessing the properties of the class user.sayHello(); user.printrollNo();
On compiling, it will generate the following JavaScript code:
// Defining a class class Student { // Declaring the properties of the class constructor(name, rollNo) { this.name = name; this.rollNo = rollNo; } // Defining the methods of the class sayHello() { console.log("Start: sayHello"); console.log(`Hello, my name is ${this.name}.`); console.log("End: sayHello"); } printrollNo() { console.log("Start: printrollNo"); console.log(`My RollNo is ${this.rollNo}.`); console.log("End: printrollNo"); } } // Creating an object of the class const user = new Student("John", 20); // Accessing the properties of the class user.sayHello(); user.printrollNo();
It will produce the following output:
Start: sayHello Hello, my name is John. End: sayHello Start: printrollNo My RollNo is 20. End: printrollNo
What if we want to reuse the logic of logging the function execution without writing the repeated code? Here, decorators come into the picture.
Let”s learn it via the example below.
// Decorator factory function printExecution(method: any, _context: any) { // Returning a new function return function (value: any, ...args: any[]) { // Logging the method name at the start console.log("start:", method.name); // Calling the original method const result = method.call(value, ...args); // Logging the method name at the end console.log("end:", method.name); return result; } } // Defining a class class Student { // Declaring the properties of the class constructor(private name: string, private rollNo: number) { } // Defining the methods of the class @printExecution sayHello() { console.log(`Hello, my name is ${this.name}.`); } @printExecution printrollNo() { console.log(`My RollNo is ${this.rollNo}.`); } } // Creating an object of the class const user = new Student("John", 20); // Accessing the properties of the class user.sayHello(); user.printrollNo();
The above code prints the same output as the previous code.
Start: sayHello Hello, my name is John. End: sayHello Start: printrollNo My RollNo is 20. End: printrollNo
Class Decorators
Class decorators are used with the class declaration to observe or modify the class definition.
Example
// Decorator factory function LogClass(target: Function) { console.log(`${target.name} is instantiated`); } // Decorator @LogClass class MyClass { constructor() { console.log("MyClass instance created"); } } // Create an instance of the class const myClassInstance = new MyClass();
On compilation, it will generate the following JavaScript code:
// Class definition class MyClass { constructor() { console.log("MyClass instance created"); } } // Decorator LogClass(MyClass); // Create an instance of the class const myClassInstance = new MyClass();
Output
MyClass instance created MyClass is instantiated
Method Decorators
The method decorators are used to replace, modify, or observe the method definition. It is used with the method definition.
Example
// Decorator factory function printExecution(method: any, _context: any) { // Returning a new function return function (value: any, ...args: any[]) { // Logging the method name at the start console.log("start:", method.name); // Calling the original method const result = method.call(value, ...args); return result; } } // Defining a class class Person { constructor(private name: string) { } // Decorator @printExecution printName() { console.log(`Hello, my name is ${this.name}.`); } } // Create an object of the class const user = new Person("John"); // Accessing the properties of the class user.printName();
Output
start: printName Hello, my name is John.
Accessor Decorators
Accessor decorators are used with the get() and set() accessors to observe, replace, and modify the definition of the accessor.
Example
// Decorator factory function LogAccessor(method: any) { console.log("Getter called"); } // Define a class class MyClass { private _name: string = "MyClass"; // Decorator @LogAccessor get name() { return this._name; } } const instance = new MyClass(); console.log(instance.name);
Output
Getter called MyClass
Property Decorators
Property decorators are used with the property to modify, replace, and observe it.
Example
// Decorator function to log the property name function LogProperty(target: any, propertyKey: string) { console.log(`Property declared: ${propertyKey}`); } class Person { // Decorator is applied to the property @LogProperty name: string; @LogProperty age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } let person = new Person(''Jay'', 23);
Output
Property declared: name Property declared: age
Parameter Decorators
Parameter decorators are used with the method parameters to observe, modify, and replace them.
Example
// Decorator with parameter function LogParameter(target: any, methodName: string, parameterIndex: number) { console.log(`Parameter in ${methodName} at index ${parameterIndex} has been decorated`); } // Class decorator class MyClass { myMethod(@LogParameter param: string) { console.log(`Executing myMethod with param: ${param}`); } } // Create an instance of the class const instance = new MyClass(); instance.myMethod("test");
Output
Parameter in myMethod at index 0 has been decorated Executing myMethod with param: test
We have learned to create custom decorators in TypeScript. However, users can use the pre-defined decorators for various purposes like debugging the code, etc.
”;