Mastering TypeScript Decorators: A Beginner-to-Advanced Guide

Blog

Posted by Nuno Marques on 13 Feb 2025

Introduction

TypeScript decorators are a powerful feature that allows developers to enhance classes, methods, properties, and parameters with reusable metadata and functionality. They are widely used in frameworks like Angular to define components, services, and modules efficiently. If you’ve ever wondered how decorators work, when to use them, and how they can streamline your TypeScript projects, this guide is for you.

Estimated reading time: 8 min


What Are TypeScript Decorators?

Decorators in TypeScript are functions that modify the behavior of classes, methods, properties, or parameters at design time. They are similar to annotations in Java or attributes in C#.

Syntax:

function MyDecorator(target: any) {
  console.log("Decorator called on:", target);
}

@MyDecorator
class Example {}

Here, @MyDecorator is applied to the Example class, and MyDecorator logs information about the class.

Enabling Decorators in TypeScript

To use decorators, enable the experimentalDecorators option in tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Types of Decorators in TypeScript

TypeScript provides different types of decorators:

1. Class Decorators

A class decorator is applied to an entire class and can modify its behavior or add metadata.

function Logger(constructor: Function) {
  console.log(`Class ${constructor.name} initialized`);
}

@Logger
class Person {
  constructor() {
    console.log("Person instance created");
  }
}

📌 Use case: Logging class instantiations.


2. Method Decorators

Method decorators allow modification of methods within a class.

function LogMethod(target: any, methodName: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Method ${methodName} called with args:`, args);
    return originalMethod.apply(this, args);
  };
}

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

const calc = new Calculator();
calc.add(2, 3);  // Logs method call and arguments

📌 Use case: Debugging and logging function calls.


3. Property Decorators

Property decorators can modify or track properties within a class.

function ReadOnly(target: any, propertyName: string) {
  Object.defineProperty(target, propertyName, {
    writable: false,
    configurable: false
  });
}

class User {
  @ReadOnly
  name: string = "John";
}

const user = new User();
user.name = "Mike"; // Error: Cannot assign to 'name' because it is a read-only property.

📌 Use case: Making class properties immutable.


4. Parameter Decorators

Parameter decorators provide metadata about method parameters.

function LogParameter(target: any, methodName: string, paramIndex: number) {
  console.log(`Parameter at index ${paramIndex} in method ${methodName} is being logged.`);
}

class Demo {
  method(@LogParameter param: string) {
    console.log(param);
  }
}

📌 Use case: Validating or tracking function parameters.


5. Accessor Decorators

Accessor decorators modify or log getters and setters.

function LogAccessor(target: any, methodName: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.get;
  descriptor.get = function () {
    console.log(`Getter for ${methodName} called`);
    return originalMethod?.apply(this);
  };
}

class Product {
  private _price: number = 100;

  @LogAccessor
  get price() {
    return this._price;
  }
}

const p = new Product();
console.log(p.price);  // Logs message before returning price

📌 Use case: Monitoring property access in debugging scenarios.


Real-World Use Cases

1. Logging and Debugging

Logging function calls or API requests without modifying the original logic.

2. Validation

Ensuring function parameters meet specific conditions.

3. Dependency Injection

Injecting dependencies in frameworks like Angular.


TypeScript Decorators in Angular

Angular makes heavy use of decorators for defining components, services, and modules.

Component Decorators

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<h1>Welcome to Angular</h1>`
})
export class AppComponent {}

Service Decorators

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class DataService {
  fetchData() {
    return ["Item1", "Item2"];
  }
}

Module Decorators

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  bootstrap: [AppComponent]
})
export class AppModule {}

Key Takeaways

  • Decorators provide metadata and modify the behavior of classes, methods, and properties.
  • There are five main types of decorators in TypeScript: class, method, property, parameter, and accessor decorators.
  • Decorators are widely used in Angular for defining components, services, and modules.
  • Practical use cases include logging, validation, and dependency injection.

Next Steps

  • Experiment with creating custom decorators.
  • Explore advanced decorators with metadata reflection using reflect-metadata.
  • Learn how decorators interact with dependency injection in Angular.