TypeScript Decorators: A complete guide
alakkadshaw
Posted on June 10, 2023
Decorators are awesome features, Many libraries are made out of decorators like react and angular. These are an awesome concept
In this article we are going to learn about typescript decorators.
What are TypeScript Decorators?
Decorators are basically just functions at their core.
With decorators you can apply re-useable behaviors to classes, methods, properties. Decorators are inspired by decorators in other languages like Java and Python
Dead Simple Chat offers Javascript Chat API and SDK to add in-app chat to your React applications in minutes. Dead Simple Chat is a highly customizable chat solution and can be used for any chat use case.
Types of Decorators
These Decorators can apply to
- Class
- Method
- Class Property
- Accessor and
- Method Parameter
- Class decorator
Class decorator is applied to the constructor of a class and can be used to observe, modify and change a class definition
example of a class decorators
type ClassDecorator = <TFunction extends Function>
(target: TFunction) => TFunction | void;
- @Params
target The constructor of the class
@Returns
If the decorator returns a value. It replaces the class definition and it is a way to extend class definition with new properties and methods
Let us look at this with the help of an example
function TechClass(constructor: Function) {
console.log(`Class Name: ${constructor.name}`);
}
@TechClass
class CoolClass {
constructor() {
console.log('New Class instance has beed created');
}
}
Here TechClass is a class decorator when the is created the decorator is triggered and the class name that is CoolClass is logged to the console
2. Method Decorators
Method decorators are used for methods, they can change and replace or observe a functions definition
Let us learn more about method decorators with an example:
function Techfunc(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const realMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`function Name: ${propertyName}`);
return realMethod.apply(this, args);
};
}
class CoolClass {
@Techfunc
testFunc() {
console.log('from testFunc');
}
}
const instance = new CoolClass();
instance.testFunc();
Here the techfunc
decorator logs the functions name to the console
. the TestFunc
method of the CoolClass
triggers the TechFunc decorator which in turn logs the function name to the console
let us look at another example
function CoolLogs(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
console.log('params: ', ...args);
const result = original.call(this, ...args);
console.log('const result: ', result);
return result;
}
}
class C {
@CoolLogs
add(a: number, b:number ) {
return a + b;
}
}
const c = new C();
c.add(1, 2);
// -> params: 1, 2
// -> result: 3
Here we have a CoolLogs
function that serves as a decorator for the class named c
Our decorator takes 3 parameters
target
propertyKey
descriptor
Inside the const
original the original value of the function is stored.
then the original function is replaced by another function that takes a number of arguments
then it calls the new function with the same arguments
Property Decorators
Property descriptors can be used to change or observer a properties property definition
Dead Simple Chat offers Javascript Chat API and SDK to add in-app chat to your React applications in minutes. Dead Simple Chat is a highly customizable chat solution and can be used for any chat use case.
for example
function TestProperty(target: any, propertyName: string) {
console.log(`Name of the Property: ${propertyName}`);
}
class MyClass {
@TestProperty
CoolProperty: string;
constructor() {
this.CoolProperty = 'Aweseome, we are so happy';
}
}
Here the TestProperty
defines the TestProperty
decorator that observes a ClassProperty
with the type string
whenever a new class instance is created the Test property logs the name of the property to the console
Parameter Decorator
Parameter decorators are used to watch and change the functional or methods parameters
let us look at the parameter decorator with an example
function TestParameter(target: any, propertyName: string, CoolParameter: number) {
console.log(`Cool Parameter: ${CoolParameter}`);
}
class SomeClass {
exampleMethod(@TestParameter saySomething: string) {
console.log(`from exampleMethod: ${saySomething}`);
}
}
const instance = new SomeClass();
instance.exampleMethod('Hello');
Here we have a function TestParameter
that console.logs
the CoolParameter
parameter
and then we have SomeClass with a method named exampleMethod
. When the exampleMethod
is called the decorator is triggered and the TestParameter
function is called which then logs the CoolParameter
to the console
Accessor Decorators
Accessor decorators are like method decorators but the only difference is the that the Method decorators have keys in their descriptor like
- Value
- writable
- configurable
- enumerable
The descriptor in the Accessor decorators has keys
- get
- set
- enumerable
- configurable
The accessor decorators accept the follow thee parameters
- Constructor function or target prototype
- property name
- property descriptor Dead Simple Chat offers Javascript Chat API and SDK to add in-app chat to your React applications in minutes. Dead Simple Chat is a highly customizable chat solution and can be used for any chat use case.
When to use Decorators
- Before/After Hooks
- Watch property changes and method calls.
- Transform parameters
- Add extra method or properties
- Runtime type validation
- Auto serialization and deserialization
- Dependency Injection
1. Before /After Hooks
Before / After hooks means the decorator is called before and after the function is called which is quite useful in debugging, logging, and measuring performance
Let us look into it through an example
function ExampleOfBeforeAfterHookFunction(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Before: ${propertyName}`);
const result = originalMethod.apply(this, args);
console.log(`After: ${propertyName}`);
return result;
};
}
class SomeClass {
@ExampleOfBeforeAfterHookFunction
coolFunction() {
console.log('Inside coolFunction');
}
}
const instance = new SomeClass();
instance.coolFunction();
Here we have the ExampleOfBeforeAfterHookFunction
function
where we are storing the originalMethod
in the orignalMethod
const
Then we replace the original
method with a new function implement that takes n number of arguments using the ...args
property
then we log a before property name and then we call the originalMethod
function and then we log the after property name.
This is how you can implement the before / after hooks method
2. Watch property changes and method calls.
Watch is a property decorator that logs the property as well as the method calls of a decorated method
This is especially useful when debugging and performance monitering.
Let us learn more about Watch property and how it works using an example
function see(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`function has been called: ${propertyName} with args:`, args);
return originalMethod.apply(this, args);
};
}
class SomeClass {
myProperty: string;
@see
setProperty(value: string) {
console.log('Set the value to ');
this.myProperty = value;
}
}
const instance = new SomeClass();
instance.setProperty('Awesome Great work Done');
3. Transform Parameters
In transform parameters function applies the transformation function to a specific parameter before the method is called
Let us learn more about transform parameters with the help of an example
function CoolFunc(SomeFunc: (arg: any) => any): ParameterDecorator {
return (target: any, propertyKey: any, parameterIndex:any) => {
const originalMethod = target[propertyKey];
target[propertyKey] = function (...args: any[]) {
args[parameterIndex] = SomeFunc(args[parameterIndex]);
return originalMethod.apply(this, args);
};
};
}
class MyClass {
myMethod(@CoolFunc((x) => x * 2) num: number) {
console.log(`Transformed parameter: ${num}`);
}
}
const instance = new MyClass();
instance.myMethod(5);
What is happening here
The method decorator see logs the names and arguments of the decorated method
When the method see is called in the instance of MyClass it logs the method name, and sets the property value of myproperty
4. Add extra method or property
You can add an extra method or property using the decorators. Here is how you can achieve this using an example
function AddMethod() {
return function (constructor: Function) {
constructor.prototype.AddMethod = function () {
console.log('Added a new method the property by the decorator');
};
};
}
@AddMethod()
class SomeClass {}
const instance = new SomeClass();
(instance as any).AddMethod();
the code has a function called AddMethod
which adds a function ot the prototype of the decorated class which in this case is SomeClass
When an instance of SomeClass
is called the addMethod
can be called on it. The add method logs a message to the console
5. Runtime Type Validation
Runtime type validation is ensuring the type of values of the parameters and variables and const and data used in the function during run time matches the values of the expected type
unlike static type validation which happens during software compilation the run time type validation happens when the function or programing is running thus it is helpful in validating external input data
Let us learn more about Runtime Type Validation using an example
function SomeFunc(target: any, propertyKey: string, parameterIndex: number) {
const originalMethod = target[propertyKey];
target[propertyKey] = function (...args: any[]) {
if (typeof args[parameterIndex] !== 'number') {
throw new Error(`An error has occoured ${parameterIndex}`);
}
return originalMethod.apply(this, args);
};
}
class TestClass {
calculateArea(@SomeFunc radius: number) {
return Math.PI * radius * radius;
}
}
const instance = new TestClass();
try {
instance.calculateArea('this is NaN');
} catch (error) {
console.error(error.message);
}
console.log(instance.calculateArea(6));
6. Auto serialization and deserialization
Auto serialization and deserialization refers to the practice of converting from one data type to another data type deserialization in order to store the data in a particular format
then converting again from one data type to the orignal data type for the purpose of rendering the data or other function can consume the data
Here is an example explaining how decorators can be used to do this
function SerializeFunc(constructor: Function) {
constructor.prototype.serialize = function () {
return JSON.stringify(this);
};
constructor.deserialize = function (json: string) {
return new constructor(JSON.parse(json));
};
}
function DeserializeFunc(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (serializedData: string) {
const deserializedData = JSON.parse(serializedData);
return originalMethod.apply(this, deserializedData);
};
}
@SerializeFunc
class Person {
constructor(public name: string, public age: number) {}
@SerializeFunc
greet(data: { name: string; age: number }) {
return `Awesome, people call me ${data.name} my age is ${data.age} years old.`;
}
}
const man = new Man('Jim Class', 43);
const serializedMan = man.serialize();
console.log(serializedMan); // {"name":"Jim Class","age":43}
const deserializedMan = (Man as any).deserialize(serializedMan);
console.log(deserializedMan.greet());
const serializedData = '{"name":"Don markell","age":56}';
console.log(man.greet(serializedData));
7. Dependency Injection
Decorators can also be used for dependency injection. Dependency injection is the practice of providing the functions and Objects that a method depends upon to it as parameter
this helps in decoupling the code and makes it cleaner
Decorators helps in dependency injection by providing a way to inject dependencies directly into class constructors without changing class implementation
This making them easier to maintain and test
Dead Simple Chat offers Javascript Chat API and SDK to add in-app chat to your React applications in minutes. Dead Simple Chat is a highly customizable chat solution and can be used for any chat use case.
Advantages of using decorators
1. Cross Cutting concerns
When parts of a program relay on many other parts of the program. Like a program might relay on other classes methods or properties then this could be a use case for decorators and they provide an easy way to maintain the code and provide improved readability
2. Dependency Injection
Dependency injection is a software design pattern where an object or a function that relies on other objects or functions receives then
This is like an inversion of control and results in loosely coupled program and a code base that is modular
Here also decorators can in useful
3. Validation
Decorators can be used to validate inputs or the state of an Object.
4. Code organization
The code that doesn't belong in the main function or logic can be encapsulated using decorators thus making the code more readable and maintainable
Dead Simple Chat offers Javascript Chat API and SDK to add in-app chat to your React applications in minutes. Dead Simple Chat is a highly customizable chat solution and can be used for any chat use case.
Conclusion
In this article we have learnt that how to use typescript decorators and we also have used a number of examples to make them easier to understand
I hope you find this article useful
Thanks for reading the article
Posted on June 10, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.