Decorators

Back

Loading concept...

🎨 TypeScript Decorators: Magic Stickers for Your Code

Imagine you have a plain notebook. Now imagine you can stick magical stickers on it that give it superpowers—like making it glow, talk, or remember things. That’s exactly what decorators do in TypeScript!


🌟 What Are Decorators? (The Big Picture)

Think of decorators like special labels you stick on things to give them new abilities.

Real Life Example:

  • You have a plain white t-shirt 👕
  • You stick a “Glow in the Dark” label on it ✨
  • Now the t-shirt glows at night!

In TypeScript, decorators are those magical labels. You stick them on:

  • Classes (the whole t-shirt)
  • Methods (the sleeves)
  • Properties (the buttons)
  • Parameters (the thread)
@GlowInTheDark  // ← This is a decorator!
class TShirt {
  // Now TShirt has superpowers!
}

The @ symbol is like saying “Hey, stick this label here!”


🏠 Decorators Overview

How Do They Work?

A decorator is just a function that receives information about what it’s decorating.

graph TD A["You write @something"] --> B["TypeScript sees the @"] B --> C["Calls your decorator function"] C --> D["Your function adds magic!"]

The Golden Rule

Decorators run when your code loads, NOT when you use the class!

Think of it like:

  • You put stickers on toys at the factory 🏭
  • When someone buys the toy, stickers are already there
  • You don’t add stickers when playing with the toy

Enabling Decorators

Before using decorators, tell TypeScript “I want to use magic stickers!”

In your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

🎪 Class Decorators: Decorating the Whole Thing

A class decorator is like wrapping a gift box. You take the whole box and add something to it.

Simple Example

function Frozen(target: Function) {
  // Make the class unchangeable
  Object.freeze(target);
  console.log(`${target.name} is frozen!`);
}

@Frozen
class IceCream {
  flavor = "vanilla";
}
// Output: "IceCream is frozen!"

What happened?

  1. We created a decorator called Frozen
  2. We stuck it on IceCream class
  3. Now nobody can add new things to IceCream!

Real-World Example: Adding Features

function WithTimestamp(target: Function) {
  target.prototype.createdAt = new Date();
  target.prototype.showAge = function() {
    console.log(`Born on: ${this.createdAt}`);
  };
}

@WithTimestamp
class Document {
  title = "My Doc";
}

const doc = new Document();
(doc as any).showAge();
// Output: "Born on: [current date]"

The Magic: We added createdAt and showAge() to EVERY Document, without writing them inside the class!


🔧 Method Decorators: Superpowers for Functions

Method decorators let you wrap or modify what a function does.

The Three Gifts

When you decorate a method, TypeScript gives you three things:

Gift What It Is Like…
target The class The whole toy box
propertyKey Method name Label on the toy
descriptor Method details Instructions sheet

Simple Example: Logging

function LogIt(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;

  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey}...`);
    const result = original.apply(this, args);
    console.log(`Done! Result: ${result}`);
    return result;
  };
}

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

const calc = new Calculator();
calc.add(2, 3);
// Output:
// "Calling add..."
// "Done! Result: 5"

What happened? We wrapped the add method with extra code that runs before and after!

Another Example: Timing

function Timer(
  target: any,
  key: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;

  descriptor.value = function(...args: any[]) {
    const start = Date.now();
    const result = original.apply(this, args);
    const end = Date.now();
    console.log(`${key} took ${end - start}ms`);
    return result;
  };
}

class DataProcessor {
  @Timer
  processData() {
    // Some heavy work...
    for(let i = 0; i < 1000000; i++) {}
    return "done";
  }
}

📦 Property Decorators: Labels for Your Stuff

Property decorators help you watch or control class properties.

What You Get

Gift What It Is
target The class prototype
propertyKey Name of the property

Simple Example: Required Fields

function Required(target: any, propertyKey: string) {
  let value: any;

  Object.defineProperty(target, propertyKey, {
    get() {
      return value;
    },
    set(newValue) {
      if (newValue === undefined || newValue === null) {
        throw new Error(`${propertyKey} is required!`);
      }
      value = newValue;
    }
  });
}

class User {
  @Required
  name!: string;

  @Required
  email!: string;
}

const user = new User();
user.name = "Alice";     // ✅ Works fine
user.email = null;       // ❌ Error: email is required!

Another Example: Auto-Format

function Uppercase(target: any, propertyKey: string) {
  let value: string;

  Object.defineProperty(target, propertyKey, {
    get() {
      return value;
    },
    set(newValue: string) {
      value = newValue.toUpperCase();
    }
  });
}

class Message {
  @Uppercase
  text!: string;
}

const msg = new Message();
msg.text = "hello world";
console.log(msg.text); // "HELLO WORLD"

🎯 Parameter Decorators: Watching the Inputs

Parameter decorators let you mark and track function inputs.

What You Get

Gift What It Is
target The class prototype
propertyKey Method name
parameterIndex Position of the parameter (0, 1, 2…)

Simple Example: Marking Important Params

const requiredParams: Map<string, number[]> = new Map();

function Important(
  target: any,
  methodName: string,
  paramIndex: number
) {
  const existing = requiredParams.get(methodName) || [];
  existing.push(paramIndex);
  requiredParams.set(methodName, existing);
  console.log(
    `Parameter ${paramIndex} of ${methodName} is important!`
  );
}

class OrderService {
  placeOrder(
    @Important customerId: string,
    @Important productId: string,
    quantity: number
  ) {
    return `Order placed!`;
  }
}
// Output:
// "Parameter 0 of placeOrder is important!"
// "Parameter 1 of placeOrder is important!"

Combining with Method Decorators

Parameter decorators collect info. Method decorators use that info!

const validatedParams: number[] = [];

function Validate(
  target: any,
  methodName: string,
  paramIndex: number
) {
  validatedParams.push(paramIndex);
}

function ValidateAll(
  target: any,
  methodName: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;

  descriptor.value = function(...args: any[]) {
    for (const index of validatedParams) {
      if (args[index] === undefined) {
        throw new Error(`Argument ${index} is missing!`);
      }
    }
    return original.apply(this, args);
  };
}

class API {
  @ValidateAll
  fetchUser(@Validate id: string) {
    return { id, name: "User" };
  }
}

🎭 Execution Order: Who Goes First?

When you have multiple decorators, they run in a specific order:

graph TD A["1. Parameter Decorators"] --> B["2. Method Decorators"] B --> C["3. Property Decorators"] C --> D["4. Class Decorators"]

Memory Trick: “Params → Methods → Props → Class” Think: People Make Pizza Crusts

Multiple Decorators on Same Thing

They run bottom to top:

@First   // Runs SECOND
@Second  // Runs FIRST
class Example {}

It’s like putting on clothes:

  • Put on shirt first (bottom decorator)
  • Then jacket (top decorator)

🎁 Quick Summary

Decorator Type Decorates Use Case
Class Whole class Add features, freeze, log
Method Functions Time, log, validate
Property Variables Auto-format, validate
Parameter Inputs Mark, track, validate

🚀 You Did It!

You now understand TypeScript decorators! They’re like magical stickers that give your code superpowers:

  • Class decorators = Gift wrapping the whole present
  • Method decorators = Adding a surprise mechanism
  • Property decorators = Labeling what’s inside
  • Parameter decorators = Checking the ingredients

Next time you see @something, you’ll know: “That’s just a magic sticker adding superpowers!”

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.