TypeScript Decorators: A complete guide
Tags: typescript, webdev, javascript, programming
Published: 2023-06-10 15:42:56

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

1. **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

```javascript
type ClassDecorator = <TFunction extends Function>
  (target: TFunction) => TFunction | void;
```

* @Params
1. 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

```javascript
  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:

```javascript
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

```javascript
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](https://deadsimplechat.com/javascript-chat-api-sdk) 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

```javascript
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

```javascript
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

```javascript
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

```javascript
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

```javascript
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

```javascript
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

```javascript
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

```javascript
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](https://deadsimplechat.com/blog/typescript-decorators-a-complete-guide/) 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