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.
functiondecoratorName(args:string){// Decorator factory returns the function expressionreturnfunction(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 classclassStudent{// Declaring the properties of the classconstructor(private name:string,private rollNo:number){}// Defining the methods of the classsayHello(){console.log(Hello, my name is ${this.name}.);}printrollNo(){console.log(My RollNo is ${this.rollNo}.);}}// Creating an object of the classconst user =newStudent("John",20);// Accessing the properties of the classuser.sayHello();
user.printrollNo();
On compiling, it will generate the following JavaScript code:
// Defining a classclassStudent{// Declaring the properties of the classconstructor(name, rollNo){this.name = name;this.rollNo = rollNo;}// Defining the methods of the classsayHello(){console.log(Hello, my name is ${this.name}.);}printrollNo(){console.log(My RollNo is ${this.rollNo}.);}}// Creating an object of the classconst user =newStudent("John",20);// Accessing the properties of the classuser.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 classclassStudent{// Declaring the properties of the classconstructor(private name:string,private rollNo:number){}// Defining the methods of the classsayHello(){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 classconst user =newStudent("John",20);// Accessing the properties of the classuser.sayHello();
user.printrollNo();
On compiling, it will generate the following JavaScript code:
// Defining a classclassStudent{// Declaring the properties of the classconstructor(name, rollNo){this.name = name;this.rollNo = rollNo;}// Defining the methods of the classsayHello(){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 classconst user =newStudent("John",20);// Accessing the properties of the classuser.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 factoryfunctionprintExecution(method:any, _context:any){// Returning a new functionreturnfunction(value:any,...args:any[]){// Logging the method name at the startconsole.log("start:", method.name);// Calling the original methodconst result =method.call(value,...args);// Logging the method name at the endconsole.log("end:", method.name);return result;}}// Defining a classclass Student {// Declaring the properties of the classconstructor(private name:string,private rollNo:number){}// Defining the methods of the class@printExecutionsayHello(){console.log(Hello, my name is ${this.name}.);}@printExecutionprintrollNo(){console.log(My RollNo is ${this.rollNo}.);}}// Creating an object of the classconst user =newStudent("John",20);// Accessing the properties of the classuser.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 factoryfunctionLogClass(target:Function){console.log(${target.name} is instantiated);}// Decorator@LogClassclassMyClass{constructor(){console.log("MyClass instance created");}}// Create an instance of the classconst myClassInstance =newMyClass();
On compilation, it will generate the following JavaScript code:
// Class definitionclassMyClass{constructor(){console.log("MyClass instance created");}}// DecoratorLogClass(MyClass);// Create an instance of the classconst myClassInstance =newMyClass();
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 factoryfunctionprintExecution(method:any, _context:any){// Returning a new functionreturnfunction(value:any,...args:any[]){// Logging the method name at the startconsole.log("start:", method.name);// Calling the original methodconst result =method.call(value,...args);return result;}}// Defining a classclass Person {constructor(private name:string){}// Decorator@printExecutionprintName(){console.log(Hello, my name is ${this.name}.);}}// Create an object of the classconst user =newPerson("John");// Accessing the properties of the classuser.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 factoryfunctionLogAccessor(method:any){console.log("Getter called");}// Define a classclass MyClass {private _name:string="MyClass";// Decorator@LogAccessorgetname(){returnthis._name;}}const instance =newMyClass();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 namefunctionLogProperty(target:any, propertyKey:string){console.log(Property declared: ${propertyKey});}classPerson{// 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 =newPerson('Jay',23);</code></pre>
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 parameterfunctionLogParameter(target:any, methodName:string, parameterIndex:number){console.log(Parameter in ${methodName} at index ${parameterIndex} has been decorated);}// Class decoratorclassMyClass{myMethod(@LogParameter param:string){console.log(Executing myMethod with param: ${param});}}// Create an instance of the classconst instance =newMyClass();
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.
Leave a Reply