# The Ultimate guide to Decorators in Typescript

## **What are Decorators**

Decorators are a powerful feature in TypeScript that allows developers to **modify** or **extend** the behaviour of **classes**, **methods**, **accessors**, and **properties**. They offer an elegant way to add functionality or modify the behaviour of existing constructs without altering their original implementation.

More formally -

A *Decorator* is a special kind of declaration that can be attached to a [class declaration](https://www.typescriptlang.org/docs/handbook/decorators.html#class-decorators), [method](https://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators), [accessor](https://www.typescriptlang.org/docs/handbook/decorators.html#accessor-decorators), [property](https://www.typescriptlang.org/docs/handbook/decorators.html#property-decorators), or [parameter](https://www.typescriptlang.org/docs/handbook/decorators.html#parameter-decorators). Decorators use the form `@expression`, where the `expression` must evaluate to a function that will be called at runtime with information about the decorated declaration.

## **The History of Decorators**

Decorators have a rich history in the TypeScript and JavaScript ecosystems. The concept of decorators was inspired by Python and other programming languages. The initial decorator's proposal for JavaScript was introduced in 2014, and since then, several versions of the proposal have been developed, with the current one being at stage 3 of the ECMAScript standardization process which is now officially supported by Typescript 5.0.

To enable experimental support for decorators, you must enable the [`experimentalDecorators`](https://www.typescriptlang.org/tsconfig#experimentalDecorators) compiler option either on the command line or in your `tsconfig.json`:

**Command Line**:

```bash
tsc --target ES5 --experimentalDecorators
```

**tsconfig.json**:

```json
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}
```

## **Decorator Functions and Their Capabilities**

A decorator is a function that takes the construct being decorated as its argument and may return a modified version of the construct or a new construct altogether. Decorators can be used to:

* Modify the behaviour of a class, method, accessor, or property
    
* Add new functionality to a class or method
    
* Provide metadata for a construct
    
* Enforce coding standards or best practices
    

## Decorator Factories

If we want to customize how a decorator is applied to a declaration, we can write a decorator factory. A *Decorator Factory* is simply a function that returns the expression that will be called by the decorator at runtime.

We can write a decorator factory in the following fashion:

```typescript
function color(value: string) {  
// this is the decorator factory, it sets up  
// the returned decorator function  
    return function (target) {    
        // this is the decorator    
        // do something with 'target' and 'value'...  
    };
}
```

## Decorator Evaluation

There is a well-defined order to how decorators applied to various declarations inside of a class are applied:

1. *Parameter Decorators*, followed by *Method*, *Accessor*, or *Property Decorators* are applied for each instance member.
    
2. *Parameter Decorators*, followed by *Method*, *Accessor*, or *Property Decorators* are applied for each static member.
    
3. *Parameter Decorators* are applied for the constructor.
    
4. *Class Decorators* are applied for the class.
    

## Types of Decorators

Decorators can be categorised into the following types -

1. Class Decorators
    
2. Method Decorators
    
3. Getter and Setter (Property) Decorators
    
4. Field (Parameter) Decorators
    
5. Auto-accessor Decorators
    

### **Class Decorators**

Class decorators are applied to class constructors and can be used to modify or extend the behaviour of a class. Some common use cases for class decorators include:

* Collecting instances of a class
    
* Freezing instances of a class
    
* Making classes function-callable
    

```typescript
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class BugReport {
  type = "report";
  title: string;
 
  constructor(t: string) {
    this.title = t; // cannot perform this action now.
  }
}
```

### **Method Decorators**

Method decorators are applied to class methods and can be used to modify or extend the behaviour of a method. Some common use cases for method decorators include:

* Tracing method invocations
    
* Binding methods to instances
    
* Applying functions to methods
    

```typescript
function Enumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log(target);
    console.log("-- proertyKey -- ", propertyKey);
    console.log("-- descriptor -- ", descriptor);
    //make the method enumerable
    descriptor.enumerable = true;
}

class Car {
    @Enumerable
    run() {
        console.log("inside run method...");
    }
}
console.log("-- creating instance --");
let car = new Car();
console.log("-- looping --");
for (let key in car) {
    console.log("key: " + key);
}
```

### **Getter and Setter Decorators**

Getter and setter decorators are applied to class accessors, allowing developers to modify or extend their behaviour. Common use cases for getter and setter decorators include:

* Compute values lazily and cache
    
* Implementing read-only properties
    
* Validating property assignments
    

```typescript
function lazy(target: (this: This) => Return, context: ClassGetterDecoratorContext) {
  return function (this: This): Return {
    const value = target.call(this);
    Object.defineProperty(this, context.name, { value, enumerable: true });
    return value;
  };
}

class MyClass {
  private _expensiveValue: number | null = null;

  @lazy
  get expensiveValue(): number {
    this._expensiveValue ??= computeExpensiveValue();
    return this._expensiveValue;
  }
}

function computeExpensiveValue(): number {
  // Expensive computation here…
  console.log('computing...'); // Only call once
  return 42;
}

const obj = new MyClass();

console.log(obj.expensiveValue);
console.log(obj.expensiveValue);
console.log(obj.expensiveValue);
```

### **Field Decorators**

Field decorators are applied to class fields and can be used to modify or extend the behaviour of a field. Common use cases for field decorators include:

* Changing initialization values of fields
    
* Implementing read-only fields
    
* Dependency injection
    
* Emulating enums
    

```typescript
function addOne<T>(target: undefined, context: ClassFieldDecoratorContext<T, number>) {
  return function (this: T, value: number) {
    console.log('addOne: ', value); // 3
    return value + 1;
  };
}

function addTwo<T>(
  target: undefined,
  context: ClassFieldDecoratorContext<T, number>
) {
  return function (this: T, value: number) {
    console.log('addTwo: ', value); // 1
    return value + 2;
  };
}


class MyClass {
  @addOne
  @addTwo
  x = 1;
}

console.log(new MyClass().x); // 4
```

Note: multiple decorators are evaluated in the bottom to the top order.

### **Auto-Accessor Decorators**

Auto-accessors are a new language feature that simplifies the creation of getter and setter pairs. It helps avoid problems that occur when decorator authors try to replace instance fields with accessors on the prototype, because ECMAScript instance fields shadow accessors when they are mounted on the instance.

```typescript
function logged(value, context) {
  const { kind, name } = context;
  const fieldName = String(name);
  if (kind === "accessor") {
    let { get, set } = value;

    return {
      get(this) {
        console.log(`Getting ${fieldName}`);
        return get.call(this);
      },

      set(this, val) {
        console.log(`Setting ${fieldName} to ${val}`);
        return set.call(this, val);
      },

      init(initialValue) {
        console.log(`Initializing ${fieldName} with value ${initialValue}`);
        return initialValue;
      },
    };
  }
}

class User {
  @logged
  accessor name: string = "Anonymous";

  constructor(name: string) {
    this.name = name;
  }
}

const user = new User("John Doe");
console.log(user.name);
```

## **Conclusion**

Decorators are a powerful feature in TypeScript that can help you to write better, and compact code.

As of now, Decorators are a [Stage 2 proposal](https://github.com/tc39/proposal-decorators) for JavaScript and a **Phase 3 proposal** in Typescript. As the decorator's proposal continues to evolve and mature, you can gradually choose to apply it to your project.

You can find more information at - [https://github.com/microsoft/TypeScript/pull/50820](https://github.com/microsoft/TypeScript/pull/50820)
