- TypeScript Tutorial
- TypeScript Home
- TypeScript Introduction
- TypeScript Setup
- TypeScript First Program
- TypeScript vs JavaScript
- TypeScript Data Types
- TypeScript Type Inference
- TypeScript Type Annotations
- TypeScript Interfaces
- TypeScript Enums
- TypeScript Type Aliases
- TypeScript Type Assertions
- TypeScript Variables
- TypeScript Functions
- TypeScript Functions
- TypeScript Optional Parameters
- TypeScript Default Parameters
- TypeScript Rest Parameters
- TypeScript Arrow Functions
- Classes and Objects
- Introduction to Classes
- Properties and Methods
- Access Modifiers
- Static Members
- Inheritance
- Abstract Classes
- Interfaces vs Classes
- Advanced Types
- TypeScript Union Types
- TypeScript Intersection Types
- TypeScript Literal Types
- TypeScript Nullable Types
- TypeScript Type Guards
- TypeScript Discriminated Unions
- TypeScript Index Signatures
- TypeScript Generics
- Introduction to Generics
- TypeScript Generic Functions
- TypeScript Generic Classes
- TypeScript Generic Constraints
- TypeScript Modules
- Introduction to Modules
- TypeScript Import and Export
- TypeScript Default Exports
- TypeScript Namespace
- Decorators
- Introduction to Decorators
- TypeScript Class Decorators
- TypeScript Method Decorators
- TypeScript Property Decorators
- TypeScript Parameter Decorators
- Configuration
- TypeScript tsconfig.json File
- TypeScript Compiler Options
- TypeScript Strict Mode
- TypeScript Watch Mode
TypeScript Method Decorators
Method decorators in TypeScript are a powerful feature of metaprogramming. They allow you to "wrap" a method with additional logic without actually touching the code inside that method. Think of them as reusable wrappers that can handle repetitive tasks like logging, security checks, or performance profiling across your entire application. By using decorators, you can keep your business logic clean and separated from "cross-cutting concerns" like error handling or analytics.
What is a Method Decorator?
A method decorator is a simple JavaScript function that is executed at runtime. When you attach it to a method, TypeScript passes that method's details to your decorator function. This gives you the power to inspect the method, change how it works, or even replace it entirely.
A method decorator receives three specific arguments:
- Target: For an instance method, this is the prototype of the class. For a static method, it is the constructor function itself.
- PropertyKey: A string (or symbol) containing the name of the method being decorated.
- Descriptor: The
PropertyDescriptorfor the method. This is the most important part, as it contains the actual function logic in its.valueproperty.
@DoStuff, use @LogActivity or @ValidateUserRole so other developers immediately understand the decorator's intent.
Method decorators are prefixed with the @ symbol and are placed directly above the method they are meant to modify.
Enabling Method Decorators in TypeScript
By default, decorators are considered an "experimental" feature in TypeScript. To use them, you must explicitly tell the TypeScript compiler to allow them in your configuration file.
Example: Enabling Method Decorators
{
"compilerOptions": {
"target": "ES6",
"experimentalDecorators": true
}
}
- The
experimentalDecoratorsoption must be set totrue. While decorators are becoming a standard part of JavaScript, TypeScript's current implementation follows an earlier proposal that is still widely used in the industry.
tsconfig.json, you will see the error: "Experimental support for decorators is a feature that is subject to change in a future release."
Syntax of Method Decorators
When you define a decorator, the signature looks like this:
function MyDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// Logic goes here
console.log("Decorating:", propertyKey);
}
Applying Method Decorators
To apply a decorator, you simply "tag" the method. This is useful for simple tasks where you don't need to pass any configuration to the decorator.
Example: Applying a Simple Method Decorator
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(`The method ${propertyKey} has been decorated.`);
}
class User {
@Log
save() {
console.log("Saving user to database...");
}
}
const user = new User();
user.save();
- In this example, the
@Logdecorator runs as soon as the class is defined, not just when the method is called. This is a common point of confusion for beginners.
Modifying the Method Behavior
To change how a method behaves when it is called, you need to modify the descriptor.value. This is where you can intercept arguments and the return value.
Example: Modifying the Method
function Debug(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value; // Save the original function
// Replace the original function with a new one
descriptor.value = function (...args: any[]) {
console.log(`[DEBUG] Calling ${propertyKey} with:`, JSON.stringify(args));
// Call the original method and capture the result
const result = originalMethod.apply(this, args);
console.log(`[DEBUG] ${propertyKey} returned:`, result);
return result; // Return the original result so the program continues normally
};
}
class Calculator {
@Debug
add(a: number, b: number) {
return a + b;
}
}
const calc = new Calculator();
calc.add(5, 10);
// Output:
// [DEBUG] Calling add with: [5,10]
// [DEBUG] add returned: 15
- The use of
.apply(this, args)is crucial. It ensures that thethiscontext inside your method still points to the class instance.
Accessing and Modifying Method Properties
The PropertyDescriptor isn't just for the function code. It also controls how the property behaves in the JavaScript environment (whether it can be changed, deleted, or seen in loops).
Example: Read-Only Method
function ReadOnly(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.writable = false;
}
class APIClient {
@ReadOnly
connect() {
console.log("Connecting to secure server...");
}
}
const client = new APIClient();
// This will fail or throw an error in strict mode
client.connect = () => console.log("Hacked!");
- Setting
writable = falseprevents any other part of your code from overwriting this method at runtime. This is great for security-critical methods.
Method Decorators with Parameters
Sometimes you want your decorator to be configurable. To do this, you create a Decorator Factory. This is a function that returns the actual decorator function.
Example: Method Decorator with Parameters
function Authorize(role: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const userRole = "guest"; // Imagine fetching this from a session
if (userRole !== role) {
console.error(`Access Denied! Role '${role}' required.`);
return;
}
return originalMethod.apply(this, args);
};
};
}
class AdminPanel {
@Authorize("admin")
deleteUser(id: number) {
console.log(`User ${id} deleted.`);
}
}
const panel = new AdminPanel();
panel.deleteUser(99); // Output: Access Denied! Role 'admin' required.
@Authorize("admin") on one method and @Authorize("editor") on another.
Using Method Decorators for Validation
Validation is one of the most practical uses for decorators. You can intercept the arguments passed to a method and stop execution if the data is invalid.
Example: Method Decorator for Validation
function MinimumValue(min: number) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
const amount = args[0]; // Check the first argument
if (amount < min) {
throw new Error(`Transaction failed: Minimum amount is ${min}`);
}
return originalMethod.apply(this, args);
};
};
}
class BankAccount {
balance = 1000;
@MinimumValue(100)
withdraw(amount: number) {
this.balance -= amount;
console.log(`Withdrew ${amount}. New balance: ${this.balance}`);
}
}
const myAcc = new BankAccount();
myAcc.withdraw(500); // Works fine
myAcc.withdraw(10); // Throws Error: Transaction failed: Minimum amount is 100
- In this real-world scenario, the business logic (subtracting money) stays separate from the validation logic (checking the minimum amount).
Summary
- Method decorators allow you to attach metadata or modify the behavior of class methods in a clean, declarative way.
- They provide access to the target (prototype), propertyKey (name), and descriptor (the function itself).
- To change a method's logic, you must wrap the
descriptor.valueand use.apply(this, args)to maintain the correct context. - Decorator Factories (functions that return a decorator) are necessary if you need to pass custom parameters to your decorator.
- Common use cases include logging, authentication, validation, and performance monitoring.