Decorators in TypeScript

Decorators are a powerful feature in TypeScript that allows you to annotate and modify classes, methods, properties, and parameters at runtime. They provide a clean way to apply reusable behaviors or metadata to your code. Decorators are particularly useful in frameworks like Angular, where they are extensively used for dependency injection and metadata annotation.


1. What Are Decorators?

A decorator is a special kind of declaration attached to a class, method, accessor, property, or parameter. Decorators are defined using the @ symbol followed by a function.

Decorator Syntax

@decorator
class MyClass {}


2. Enabling Decorators

Decorators are an experimental feature in TypeScript and must be explicitly enabled in the tsconfig.json:

{
  "experimentalDecorators": true
}


3. Types of Decorators

TypeScript provides several types of decorators:

Decorator Type Targeted Element
Class Decorator Classes
Method Decorator Methods
Accessor Decorator Getters and setters
Property Decorator Properties
Parameter Decorator Function or constructor parameters


4. Class Decorators

A class decorator applies to a class declaration. It can modify or replace the class definition.

Example 1: Logging Class Creation

function logClass(target: Function) {
  console.log(`${target.name} class is being created!`);
}

@logClass
class MyClass {}

const instance = new MyClass(); // Logs: MyClass class is being created!

Example 2: Adding Static Properties

function addTimestamp(target: Function) {
  target.prototype.timestamp = Date.now();
}

@addTimestamp
class MyClass {}

const instance = new MyClass();
console.log((instance as any).timestamp); // Logs the timestamp


5. Method Decorators

A method decorator is applied to a method of a class. It can modify the method or add functionality.

Example: Logging Method Calls

function logMethod(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with arguments:`, args);
    return originalMethod.apply(this, args);
  };
}

class MyClass {
  @logMethod
  sayHello(name: string) {
    console.log(`Hello, ${name}!`);
  }
}

const instance = new MyClass();
instance.sayHello("Alice");
// Logs:
// Calling sayHello with arguments: [ 'Alice' ]
// Hello, Alice!


6. Property Decorators

A property decorator applies to class properties. It allows you to attach metadata or modify how the property is used.

Example: Marking Required Properties

function required(target: any, propertyKey: string) {
  console.log(`Property "${propertyKey}" is required.`);
}

class MyClass {
  @required
  name!: string;
}
// Logs: Property "name" is required.


7. Accessor Decorators

Accessor decorators are used on getters and setters of properties.

Example: Validating Property Access

function logAccess(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalGet = descriptor.get;
  descriptor.get = function () {
    console.log(`Accessing property "${propertyKey}"`);
    return originalGet?.apply(this);
  };
}

class MyClass {
  private _value = 42;

  @logAccess
  get value() {
    return this._value;
  }
}

const instance = new MyClass();
console.log(instance.value);
// Logs:
// Accessing property "value"
// 42


8. Parameter Decorators

A parameter decorator applies to the parameters of a method or constructor. It’s often used for dependency injection or metadata collection.

Example: Logging Parameter Metadata

function logParameter(
  target: any,
  propertyKey: string,
  parameterIndex: number
) {
  console.log(
    `Parameter at index ${parameterIndex} in method "${propertyKey}" is being logged.`
  );
}

class MyClass {
  greet(@logParameter name: string) {
    console.log(`Hello, ${name}`);
  }
}

const instance = new MyClass();
instance.greet("Alice");
// Logs:
// Parameter at index 0 in method "greet" is being logged.
// Hello, Alice


9. Composing Multiple Decorators

You can stack multiple decorators on a single element. Decorators are applied in reverse order (bottom to top).

function first(target: any, propertyKey: string) {
  console.log("First decorator");
}

function second(target: any, propertyKey: string) {
  console.log("Second decorator");
}

class MyClass {
  @first
  @second
  method() {}
}
// Logs:
// Second decorator
// First decorator


10. Practical Use Cases for Decorators

  • Dependency Injection: Used in frameworks like Angular for injecting dependencies into classes.
  • Logging: Automatically log class instantiation, method calls, or property accesses.
  • Validation: Mark required properties or validate method arguments.
  • Metadata: Attach additional metadata to classes or members, useful for libraries like reflect-metadata.


Summary of All Topics

Feature Description Example
Class Decorator Modifies or replaces class definitions. @logClass
Method Decorator Enhances or overrides method behavior. @logMethod
Property Decorator Adds metadata or modifies properties. @required
Accessor Decorator Applies to getters or setters to log or modify access behavior. @logAccess
Parameter Decorator Logs or injects data into parameters. @logParameter
Indexed Access Types Retrieves the type of a property dynamically. type Name = Person["name"];
Template Literal Types Constructs string types dynamically. type API = \${"GET" | "POST"} /${"users"}\
Utility Types Built-in types for manipulating types (Partial, Readonly, Pick, Omit). type PartialPerson = Partial<Person>;
Conditional Types Constructs types conditionally based on logic (extends ? :). type IsString<T> = T extends string ? true : false;
Mapped Types Creates new types by transforming existing ones (readonly, optional). type Readonly<T> = { readonly [K in keyof T]: T[K] };
Keyof and Lookup Types Dynamically access object keys and properties. type Key = keyof Person;
Generics Enables reusable, flexible code for any type. function identity<T>(arg: T): T;
Modules Allows splitting code into separate files for better organization (import/export). import { greet } from "./file";