Day 44: Decorators
Tags: javascript, typescript, 100daysofcode, day44
Published: 2023-09-08 13:30:00

In this article, we'll take a dive into decorators in TypeScript, explore their types, and provide practical examples to help you harness their full potential.

### What Are Decorators? 🎨

At their core, decorators are simply functions that are applied to classes, methods, properties, or parameters using the `@` symbol. They can be used for various purposes, including:

1. **Logging and debugging**: Adding logging statements before and after method execution.
2. **Validation**: Checking the validity of inputs or outputs.
3. **Authorization**: Controlling access to certain methods or routes.
4. **Memoization**: Caching the results of expensive operations.
5. **Dependency Injection**: Automatically injecting dependencies into classes.
6. **Metadata Collection**: Storing additional information about classes or properties.


### Class Decorators 🏛️

Class decorators are applied to classes. They receive the class constructor as their only parameter and can be used to modify the class or add metadata to it.


```typescript
function MyDecorator(target: Function) {
  console.log('Class decorator called on', target);
}

@MyDecorator
class MyClass {
  constructor() {
    console.log('MyClass constructor');
  }
}

// Output:
// Class decorator called on [Function: MyClass]
// MyClass constructor
```

### Method Decorators 🛠️

Method decorators are applied to methods within a class. They receive three parameters: the target class prototype, the method name, and a property descriptor.


```typescript
function LogMethod(target: Object, key: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${key} with arguments: ${args}`);
    const result = originalMethod.apply(this, args);
    console.log(`${key} returned: ${result}`);
    return result;
  };

  return descriptor;
}

class Calculator {
  @LogMethod
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3);

// Output:
// Calling add with arguments: 2,3
// add returned: 5
```

### Property Decorators 🏠

Property decorators are used to modify properties within a class. They receive two parameters: the target class prototype and the property name.


```typescript
function Validate(target: any, key: string) {
  let value = target[key];

  const getter = () => value;

  const setter = (newValue: any) => {
    if (typeof newValue !== 'number' || newValue < 0) {
      throw new Error('Invalid value');
    }
    value = newValue;
  };

  Object.defineProperty(target, key, {
    get: getter,
    set: setter,
  });
}

class Product {
  @Validate
  price: number;

  constructor(price: number) {
    this.price = price;
  }
}

const product = new Product(50);
product.price = -10; // Throws an error

// Output:
// Error: Invalid value
```

### Parameter Decorators 🎯

Parameter decorators are applied to parameters of methods within a class. They receive three parameters: the target class prototype, the method name, and the index of the parameter.


```typescript
const Injectable = (): ParameterDecorator => {
  return (target: any, key: string | symbol, index: number) => {
    // Register the dependency injection here
    console.log(`Injecting parameter at index ${index} in method ${key}`);
  };
};

class UserService {
  constructor(private readonly logger: Logger) {}

  @Injectable()
  log(message: string) {
    this.logger.log(message);
  }
}

const logger = new Logger();
const userService = new UserService(logger);
userService.log('Hello, Decorators!');

// Output:
// Injecting parameter at index 0 in method log
// Hello, Decorators!
```