Class Decorator in TypeScript 👋
Tags: typescript, designpatterns, decorators, oop
Published: 2023-05-25 12:45:19

Welcome 🤗 and first of all, I should mention that this article requires a background on TypeScript, TypeScript Generics and  Object Oriented Programming. 

Now, _what is a decorator?_ 🤔 In the real life, decorating something means to change its style of or its functionality. Have you ever changed the design of your room or purchased for an extensible sofa that can be converted into a bed 😅? 
In the world of programming, a **decorator** is a design pattern, it is just a function that wraps another function to add or modify some features of it.

```javascript
function decoratorFunc(fn) {
  //some code
  fn();
  //some other code
}
```

The above function `decoratorFunc` is a decorator in its simplest form. You can pass any function and add more code to be executed either before or after the main function `fn`.

So, _what are the types of **decorators**in TypeScript?
_ 🤔
1. class decorator
2. method decorator
3. accessor decorator
4. property decorator
5. parameter decorator

Which means that a decorator function can be attached to any of the above entities.

In this article we will dive deep into **class decorator**. 
To apply **decorators** they must be prefixed with the @ symbol and placed immediately before the construct to be decorated.

We now know that the **decorator** is just a function, so to create a **Class Decorator** in TypeScript we just write a function, but it must have one **argument** which is the class itself. 🤓

```javascript
function classDecorator(target: Function) {
  console.log(`This is to decorate class ${target.name}`);
}
```
This is the simplest class decorator you will ever see 😂.
The `target` parameter is the class constructor "the class that the decorator will wrap".

So let's create a class to make the example clear.

```javascript
class User {
  constructor(
    public fName: string,
    public lName: string,
  ) {}

  fullName(): string {
    return `${this.fName} ${this.lName}`;
  }
}
```

To apply the `classDecorator` decorator to the `User` class we should write

```javascript
@classDecorator
class User {
  constructor(
    public fName: string,
    public lName: string,
  ) {}

  fullName(): string {
    return `${this.fName} ${this.lName}`;
  }
}
```

Because we prefix `classDecorator` with @ and place it before the `User` class , class `User` will be passed to the `classDecorator`.

If you try to execute it, you should see the logs we print inside the decorator.
But it is important to know that the decorator will get executed on the runtime **not** on the **class instantiation**.

And voila, you create your first class decorator 🥳

What we have just written is a **Decorator Function**, what if we need to pass another argument to this function?!🤔 and it should take only one which is the class.

In this scenario we will need a **Decorator Factory Function** again, just a function the only difference that it can take any number of arguments and it returns the **decorator function** itself 👇

```javascript
function logCustomMessage(message: string) {
        return function (target: Function) {
            console.log(message)
    }
}
```
and then we pass the argument on calling it. 👇

```javascript
@logCustomMessage('This is a custom message !')
class User {
  constructor(
    public fName: string,
    public lName: string,
  ) {}

  fullName(): string {
    return `${this.fName} ${this.lName}`;
  }
}
```

Hmmm, _can a **decorator function** return values?_ 🤔
Good question 😅
From the [official documentation](https://www.typescriptlang.org/docs/handbook/decorators.html#class-decorators) 👇
If the class decorator returns a value, it will replace the class declaration with the provided constructor function.

**A decorator function can only return classes**

👉 **Use Cases** 👈
Let's introduce two examples from which we can take advantage of the decorators and know how to return a class.

Assume we're building a system for a company, so we have structured the main classes, but we need to apply some features to this class that doesn't deeply relate to the logic of the class itself. 

_Example 1:_
We create a `Department` class that has some members including the **code** of each department. This code is a unique identifier for each department and **must be unique**. So, we need to apply a **validation feature** away from the class logic to handle this case. 


The decorator we can write to validate the **code**. 👇
```javascript
function uniqueCode<C extends new (...args: any[]) => {}>(target: C) {
  return class Mixin extends target{
    static codes: string[] = [];
    // the following constructor will get executed on creating object of the original class
    constructor(...args: any[]) {
      super(args);
      const code = args[1];
      if (Mixin.codes.includes(code)) {
        throw new Error(`Department code must be unique!`);
      }
      Mixin.codes.push(code);
    }
  };
}
```

Department class 👇
```javascript
class Department {
  constructor(public name: string, public code: string) {}
}
```

And to apply the decorator on the class 👇
```javascript
@uniqueCode
class Department {
  constructor(public name: string, public code: string) {}
}
```

The `uniqueCode` decorator piece by piece 👇

- The function definition: we specify type of the argument `target`,  that it will be custom type `C` which is restricted to `new (...args: any[]) => {}`. This means that it should be a `constructor` function that we call using `new` keyword, and this `constructor` function can have any numbers of arguments, then after calling it, it will return finally an object.

- The function body: we just return a new class that extends the original class, to add more features, and there is the place we implement the **validation feature**.

Now if you try to execute 👇
```javascript
const hrDep = new Department("HR", "H259");
const marketingDep = new Department("Marketing", "H259");
```
it will throw an error.

**NOTE THAT** the class we returned from the decorator will replace the original one. Try to execute `console.log(Department.toString())` before and after applying the decorator to see the difference.

_Example 2:_
What if we need to reuse this decorator to be applied to the `Employee` class as well 🤔.
The dynamic information we need is to know the index of the **code** attribute in each class we will apply the decorator to.

By rewriting `uniqueCode` decorator and using **Decorator Factory Function** instead, we can pass extra arguments.

```javascript
function uniqueCode(codeIndex: number) {
  return function <C extends new (...args: any[]) => {}>(target: C) {
    return class Mixin extends target {
      static codes: string[] = [];
      constructor(...args: any[]) {
        super(args);
        const code = args[codeIndex];
        if (Mixin.codes.includes(code)) {
          throw new Error(`${target.name} code must be unique!`);
        }
        Mixin.codes.push(code);
      }
    };
  };
}
```
Now we can pass the index of the `code` attribute according to its definition in the original class.

Applying the dynamic decorator to any class that has code attribute 👇

```javascript
// 1 is the index of code in the constructor parameters
@uniqueCode(1) 
class Department {
  constructor(public name: string, public code: string) {}

  info() {
    return {
      name: this.name,
      code: this.code,
    };
  }
}

// 2 is the index of code in the constructor parameters
@uniqueCode(2) 
class Employee {
  constructor(
    public name: string,
    public salary: number,
    public code: string
  ) {}
}

```

And finally we are at the end 😄. You can implement great ideas using **decorators** and be safe away from the original logic of the class. Try it now 🙂