DEV Community

Cover image for 10 Game-Changing Decorator Patterns Every JavaScript Developer Should Know
Rajat
Rajat

Posted on

10 Game-Changing Decorator Patterns Every JavaScript Developer Should Know

Discover how decorators can clean up your code, enhance reusability, and help you write scalable, professional-grade Angular and JavaScript applications.


Ever looked at @Component, @Injectable, or @Input in Angular and wondered — "Can I make my own decorator magic?"

If yes, you're about to unlock one of the most elegant and underused features in TypeScript: decorators.

This article dives deep into 10 practical and powerful decorator patterns, not just theory—but hands-on, demo-style code you can apply right now in your Angular or vanilla TypeScript projects. We’ll cover class decorators, method decorators, property decorators, and accessor decorators with clarity and purpose.

At the end, you’ll know how to:

  • Create and apply custom decorators
  • Use decorators to inject behavior, log automatically, validate data, and memoize functions
  • Write cleaner, DRY-er, and scalable Angular code
  • Think like an advanced frontend developer

Let’s get started.


1. Logging Methods Automatically with a Method Decorator

function LogMethod(
  target: Object,
  propertyName: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyName} with`, args);
    const result = original.apply(this, args);
    console.log(`Returned from ${propertyName} with`, result);
    return result;
  };
}

Enter fullscreen mode Exit fullscreen mode

Usage:

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

new MathService().add(2, 3); // Logs input and output

Enter fullscreen mode Exit fullscreen mode

2. Property Validation Decorator

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

  const getter = () => value;
  const setter = (newVal: any) => {
    if (!newVal) {
      throw new Error(`${propertyKey} is required.`);
    }
    value = newVal;
  };

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

Enter fullscreen mode Exit fullscreen mode

Usage:

class User {
  @Required
  name: string;

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

new User(""); // Throws error

Enter fullscreen mode Exit fullscreen mode

3. Creating a Custom Angular Service Decorator

export function SingletonService() {
  return function (target: any) {
    Reflect.defineMetadata('singleton', true, target);
  };
}

Enter fullscreen mode Exit fullscreen mode

Usage:

@SingletonService()
@Injectable({ providedIn: 'root' })
export class MyService {}

Enter fullscreen mode Exit fullscreen mode

4. Memoize Expensive Computations

function Memoize(_: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  const cache = new Map();

  descriptor.value = function (...args: any[]) {
    const hash = JSON.stringify(args);
    if (cache.has(hash)) {
      return cache.get(hash);
    }
    const result = original.apply(this, args);
    cache.set(hash, result);
    return result;
  };
}

Enter fullscreen mode Exit fullscreen mode

Usage:

class MathEngine {
  @Memoize
  fib(n: number): number {
    if (n <= 1) return n;
    return this.fib(n - 1) + this.fib(n - 2);
  }
}

Enter fullscreen mode Exit fullscreen mode

5. Role-Based Access Control

function Role(role: string) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const userRole = 'admin'; // Mock example
      if (userRole !== role) {
        throw new Error(`Unauthorized access to ${key}`);
      }
      return original.apply(this, args);
    };
  };
}

Enter fullscreen mode Exit fullscreen mode

Usage:

class AdminPanel {
  @Role('admin')
  deleteUser(userId: string) {
    console.log(`User ${userId} deleted`);
  }
}

Enter fullscreen mode Exit fullscreen mode

6. Debounce Function Calls

function Debounce(ms: number) {
  return function (_: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    let timeout: any;

    descriptor.value = function (...args: any[]) {
      clearTimeout(timeout);
      timeout = setTimeout(() => original.apply(this, args), ms);
    };
  };
}

Enter fullscreen mode Exit fullscreen mode

Usage:

class SearchBar {
  @Debounce(300)
  onInputChange(value: string) {
    console.log('Search for', value);
  }
}

Enter fullscreen mode Exit fullscreen mode

7. Readonly Properties with a Property Decorator

function Readonly(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    writable: false,
  });
}

Enter fullscreen mode Exit fullscreen mode

Usage:

class Settings {
  @Readonly
  appName = 'MyApp';
}

Enter fullscreen mode Exit fullscreen mode

8. Auto-Bind Method to Context

function Autobind(_: any, _2: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  return {
    configurable: true,
    get() {
      return original.bind(this);
    },
  };
}

Enter fullscreen mode Exit fullscreen mode

Usage:

class ButtonHandler {
  @Autobind
  onClick() {
    console.log('Clicked by', this);
  }
}

Enter fullscreen mode Exit fullscreen mode

9. Custom @Component Wrapper in Angular

export function EnhancedComponent(metadata: Component) {
  return function (target: any) {
    Component(metadata)(target);
    // Add your enhancements here
  };
}

Enter fullscreen mode Exit fullscreen mode

Usage:

@EnhancedComponent({
  selector: 'app-enhanced',
  template: `<p>Enhanced Component</p>`,
})
export class EnhancedComponentDemo {}

Enter fullscreen mode Exit fullscreen mode

10. Custom Date Formatter Decorator

function FormatDate(_: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.get;

  descriptor.get = function () {
    const result = original!.apply(this);
    return new Date(result).toLocaleDateString();
  };
}

Enter fullscreen mode Exit fullscreen mode

Usage:

class Invoice {
  private rawDate = '2025-05-24T12:00:00Z';

  @FormatDate
  get createdAt() {
    return this.rawDate;
  }
}

Enter fullscreen mode Exit fullscreen mode

🎯 Final Thoughts

Decorators aren’t just for Angular—they’re a powerful metaprogramming tool available to all TypeScript developers. Whether you're streamlining UI logic in Angular, reducing boilerplate, or creating testable, reusable utility patterns, decorators can take your code from good to great.


🎯 Your Turn, Devs!

👀 Did this article spark new ideas or help solve a real problem?

💬 I'd love to hear about it!

✅ Are you already using this technique in your Angular or frontend project?

🧠 Got questions, doubts, or your own twist on the approach?

Drop them in the comments below — let’s learn together!


🙌 Let’s Grow Together!

If this article added value to your dev journey:

🔁 Share it with your team, tech friends, or community — you never know who might need it right now.

📌 Save it for later and revisit as a quick reference.


🚀 Follow Me for More Angular & Frontend Goodness:

I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.

  • 💼 LinkedIn — Let’s connect professionally
  • 🎥 Threads — Short-form frontend insights
  • 🐦 X (Twitter) — Developer banter + code snippets
  • 👥 BlueSky — Stay up to date on frontend trends
  • 🌟 GitHub Projects — Explore code in action
  • 🌐 Website — Everything in one place
  • 📚 Medium Blog — Long-form content and deep-dives
  • 💬 Dev Blog — Free Long-form content and deep-dives
  • ✉️ Substack — Weekly frontend stories & curated resources
  • 🧩 Portfolio — Projects, talks, and recognitions
  • ✍️ Hashnode — Developer blog posts & tech discussions
  • ✍️ Reddit — Developer blog posts & tech discussions

🎉 If you found this article valuable:

  • Leave a 👏 Clap
  • Drop a 💬 Comment
  • Hit 🔔 Follow for more weekly frontend insights

Let’s build cleaner, faster, and smarter web apps — together.

Stay tuned for more Angular tips, patterns, and performance tricks! 🧪🧠🚀

✨ Share Your Thoughts To 📣 Set Your Notification Preference

Top comments (0)