🎭 Understanding Decorators in TypeScript: The Secret Sauce to Cleaner Code


Decorators in TypeScript: The art of cleaner code
Hey there, fellow coders! 👋 Welcome back to Gocoding, where we turn complex coding concepts into fun, bite-sized lessons. Today, we’re diving into Decorators in TypeScript!
Have you ever found yourself writing the same boilerplate code over and over again? Maybe you’re adding logging to every method, validating properties, or injecting dependencies. It’s tedious, right? What if I told you there’s a way to add this functionality without cluttering your classes? Enter Decorators—the magical tool that lets you sprinkle extra behavior onto your code without messing with its core logic.
Think of decorators as stickers you can slap onto your classes, methods, or properties to give them superpowers. They’re like the secret sauce that makes your code cleaner, more reusable, and way more fun to work with. Ready to learn how they work? Let’s go!
B-b-b-but…What Are Decorators? 🤔💭
Decorators are a meta-programming feature in TypeScript. In simpler terms, they’re functions that let you write code that writes code. They’re a special kind of declaration that can be attached to classes, methods, properties, or parameters.
Here’s the cool part: decorators are executed at compile time, not runtime. That means they’re like little helpers that jump in before your code even runs, adding extra functionality or metadata.
By now you probably know what decorators are but Why Do We Need Decorators?
Imagine you’re building a class to represent a user. You want to log every time a user’s name is changed, validate the name to make sure it’s not empty, and maybe even add some fancy formatting. Without decorators, you’d have to write all that logic inside your class. It gets messy fast!
Decorators let you separate these concerns. You can write a decorator once and reuse it wherever you need it. It’s like having a Swiss Army knife for your code!
The Types of Decorators
There are five types of decorators in TypeScript, each with its own special use case. Let’s break them down one by one with examples.
1️⃣ Class Decorators
Class decorators are applied to the constructor of a class. They let you observe, modify, or even replace the class definition.
Example:
function ClassDecorator() {
return function (target: Function) {
console.log('Class Decorator Applied!');
};
}
@ClassDecorator()
class MyClass {
constructor() {
console.log('MyClass Constructor Called!');
}
}
When you apply the @ClassDecorator
to MyClass
, TypeScript calls the ClassDecorator
function as soon as the class is defined. The target
parameter is the class constructor itself. This means you can do things like adding new methods or properties to the class dynamically.
Why Use It?
Class decorators are perfect for adding global behavior to a class. For example, you could use a class decorator to automatically register all your classes in a dependency injection container.
2️⃣ Property Decorators
Property decorators are applied to the properties of a class. They let you observe or modify how properties behave.
Example:
function PropertyDecorator() {
return function (target: any, propertyName: string | symbol) {
console.log(`Property Decorator Applied to: ${propertyName}`);
};
}
class User {
@PropertyDecorator()
username: string;
constructor(username: string) {
this.username = username;
}
}
The PropertyDecorator
is called as soon as the class is defined. It receives two arguments: the target
(which is the class constructor) and the propertyName
(which is the name of the property being decorated).
Why Use It?
Property decorators are great for adding validation or logging to properties. For example, you could create a decorator that ensures a property is never set to null
.
3️⃣ Method Decorators
Method decorators are applied to the methods of a class. They let you observe, modify, or replace method definitions.
Example:
function MethodDecorator() {
return function (
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
console.log(`Method Decorator Applied to: ${methodName}`);
};
}
class Greeter {
@MethodDecorator()
greet() {
console.log('Hello from the greet method!');
}
}
The MethodDecorator
is called as soon as the class is defined. It receives three arguments: the target
(the class prototype), the methodName
(the name of the method), and the descriptor
(an object containing metadata about the method).
Why Use It?
Method decorators are perfect for adding cross-cutting concerns like logging, caching, or error handling. For example, you could create a decorator that logs every time a method is called.
4️⃣ Parameter Decorators
Parameter decorators are applied to the parameters of a method. They let you observe or modify how parameters behave.
Example:
function ParameterDecorator() {
return function (target: any, methodName: string, parameterIndex: number) {
console.log(`Parameter Decorator Applied to: ${methodName}, Index: ${parameterIndex}`);
};
}
class Greeter {
greet(@ParameterDecorator() username: string) {
console.log(`Hello, ${username}!`);
}
}
The ParameterDecorator
is called as soon as the class is defined. It receives three arguments: the target
(the class prototype), the methodName
(the name of the method), and the parameterIndex
(the position of the parameter in the method signature).
Why Use It?
Parameter decorators are great for adding validation or metadata to method parameters. For example, you could create a decorator that ensures a parameter is always a positive number.
5️⃣ Accessor Decorators
Accessor decorators are applied to the getters or setters of a property. They let you observe or modify how accessors behave.
Example:
function AccessorDecorator() {
return function (
target: any,
accessorName: string,
descriptor: PropertyDescriptor
) {
console.log(`Accessor Decorator Applied to: ${accessorName}`);
};
}
class User {
private _username = 'Hassani';
@AccessorDecorator()
get username() {
return this._username;
}
set username(value: string) {
this._username = value;
}
}
The AccessorDecorator
is called as soon as the class is defined. It receives three arguments: the target
(the class prototype), the accessorName
(the name of the getter or setter), and the descriptor
(an object containing metadata about the accessor).
Why Use It?
Accessor decorators are perfect for adding validation or logging to getters and setters. For example, you could create a decorator that logs every time a property is accessed or modified.
Decorator Composition: Stacking the Fun
You can apply multiple decorators to a single declaration. Decorators are executed from the bottom up, meaning the last decorator is executed first.
Example:
function FirstDecorator() {
return function (target: any) {
console.log('First Decorator Applied!');
};
}
function SecondDecorator() {
return function (target: any) {
console.log('Second Decorator Applied!');
};
}
@FirstDecorator()
@SecondDecorator()
class MyClass {
constructor() {
console.log('MyClass Constructor Called!');
}
}
Output:
Second Decorator Applied!
First Decorator Applied!
MyClass Constructor Called!
Why Does This Happen?
Decorators are like a stack of pancakes. The last one you add is the first one you eat!
Advanced: Meta-Programming with Decorators
Decorators can be used for meta-programming, meaning you can write code that writes code. For example, you can dynamically add properties or methods to a class.
Example:
function AdvancedDecoratorFactory(message: string) {
return function (target: any) {
console.log(`Advanced Decorator Applied with Message: ${message}`);
Object.defineProperty(target, 'message', {
value: message,
writable: true,
});
};
}
@AdvancedDecoratorFactory('Hello from the decorator!')
class MyClass {
constructor() {
console.log('MyClass Constructor Called!');
}
}
const instance = new MyClass();
console.log((instance as any).message); // Hello from the decorator!
The AdvancedDecoratorFactory
dynamically adds a message
property to the class. This is just the tip of the iceberg—you can use decorators to do some truly wild things!
Summary 📑
Here’s a quick recap of what we’ve learned:
Class Decorators: Modify or observe class definitions.
Property Decorators: Add behavior to class properties.
Method Decorators: Enhance or replace methods.
Parameter Decorators: Work with method parameters.
Accessor Decorators: Modify getters and setters.
Decorator Composition: Apply multiple decorators in a specific order.
Meta-Programming: Use decorators to write code that writes code.
Wrapping Up 🚀
Decorators are like the spices in your coding kitchen. They add flavor and functionality to your classes, methods, and properties without changing their core recipe. Whether you’re using class, property, method, parameter, or accessor decorators, they’re a powerful tool to keep your code clean, reusable, and maintainable.
So go ahead, experiment with decorators, and let your code shine! And remember, coding is all about having fun and learning along the way.
Happy coding! 🚀
TL;DR
0️⃣. Decorators are a form of meta-programming that allows you to write code that writes code. Decorators are a design pattern that is used to separate the concerns of a class. They allow you to add functionality to a class without modifying the class itself. 🧩
1️⃣. Decorators are a feature of TypeScript that allows you to add metadata to a class, method, property, or parameter. Decorators are a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. 😜
2️⃣. There are five 🖐️ types of decorators in TypeScript: Class decorators, Property decorators, Method decorators, Parameter decorators, and Accessor decorators. 😁
3️⃣. Decorators are special functions that are called by TypeScript at runtime. They are used to add behavior to a class without modifying the class itself. Decorators are created using either a decorator factory or a decorator function. Decorator factories are used to create decorators with parameters, while decorator functions are used to create decorators without parameters. 🛞🧭
4️⃣. To call a decorator, you use the @ symbol followed by the name of the decorator function or decorator factory. If the decorator has parameters, you call the decorator factory with the parameters inside the parentheses. 🚥🚦
5️⃣. Decorators are executed as soon as the class is defined, not when an instance of the class is created. This means that a decorator is called at compile time, not at runtime. The only exception is when you return a new class from a decorator. In that case, the new class is created at runtime and replaces the original class, while the decorator is executed at compile time. 🚀
6️⃣. Property decorators are applied to the property of a class. Property decorators receive two arguments: the target (the class prototype or constructor) and the name of the property being decorated. 🏛️🏡
7️⃣. Method decorators are applied to the method of a class. Method decorators receive three arguments: the target (the class prototype or constructor), the name of the method being decorated, and the property descriptor of the method. The property descriptor is an object that contains the method's metadata. ⚓
8️⃣. Parameter decorators are applied to the parameter of a method. Parameter decorators receive three arguments: the target (the class prototype or constructor), the name of the method being decorated, and the index of the parameter being decorated. 💫
9️⃣. Accessor decorators are applied to the getter or setter of a property. Accessor decorators receive three arguments: the target (the class prototype or constructor), the name of the accessor being decorated, and the property descriptor of the accessor. The property descriptor is an object that contains the accessor's metadata. 🏗️
🔟. Decorator composition is the process of applying multiple decorators to a single declaration - chaining decorators. You can apply multiple decorators to a single declaration by chaining them together. Decorator composition allows you to separate concerns and reuse decorators. 🧍♀️🧍♂️🧍🕴️
1️⃣1️⃣. The lowest decorator is executed first, and the highest decorator is executed last. This is because decorators are executed in reverse order. This is important to remember when you are using multiple decorators on the same declaration. 🚦🚥
1️⃣2️⃣. You can return a new class from a decorator. This allows you to replace the original class with a new class. This is useful when you want to modify the class definition. You can return a new class from a decorator by returning a class from the decorator function. The new class must have the same name as the original class.
Decorators in TypeScript are powerful tools that enable meta-programming by allowing you to add functionality to classes, methods, properties, and parameters without altering their core logic. They are called at compile time and can help manage boilerplate code efficiently, making your code cleaner and more maintainable. TypeScript supports five types of decorators: class, property, method, parameter, and accessor decorators. Decorators are applied using the @ symbol and can be combined for layered functionality. Embrace the power of decorators to enhance code readability and functionality, while keeping logic organized and reusable.
GOOD LUCK 😁 AND HAPPY CODING! 🚀
P.S. If you’re still reading, you’re officially a decorator pro! Now go impress your friends with your newfound knowledge. 😎
Subscribe to my newsletter
Read articles from Oohnohassani directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Oohnohassani
Oohnohassani
Hi 👋 I'm a developer with experience in building websites with beautiful designs and user friendly interfaces. I'm experienced in front-end development using languages such as Html5, Css3, TailwindCss, Javascript, Typescript, react.js & redux and NextJs 🚀