- 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 Introduction to Decorators
Decorators in TypeScript are a powerful feature that allows you to modify or extend the behavior of classes, methods, properties, and parameters at design time. If you have used frameworks like Angular or NestJS, you have already seen them in action (e.g., @Component or @Get). They are a form of meta-programming, which essentially means writing code that manages or modifies other code. By using decorators, you can add "cross-cutting concerns" like logging, validation, or dependency injection without cluttering your core logic.
What are Decorators?
A decorator is a special declaration that can be attached to a class, method, accessor, property, or parameter. Technically, a decorator is just a function that is called with specific arguments depending on where it is placed. You invoke them using the @expression syntax, where expression must evaluate to a function.
Decorators allow you to "annotate" your code. For instance, you can tell a framework that a specific class is a "Controller" or that a specific property should be hidden when the object is converted to JSON.
Enabling Decorators in TypeScript
Because decorators are not yet a standard part of ECMAScript, TypeScript requires you to explicitly opt-in to use them. You do this by modifying your tsconfig.json file.
Example: Enabling Decorators
{
"compilerOptions": {
"target": "ES6",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
experimentalDecorators: This enables the decorator syntax.emitDecoratorMetadata: This is often required by dependency injection libraries (like Inversify or NestJS) to store type information about the decorated code.
Types of Decorators
There are five main types of decorators in TypeScript, each receiving a different set of arguments:
Class Decorators
Applied to a class constructor. Use these to freeze a class, add new properties to the prototype, or replace the class entirely.
Method Decorators
Applied to methods. Great for logging execution time, checking permissions, or catching errors globally.
Accessor Decorators
Applied to getters and setters. Similar to method decorators, but used specifically for property access logic.
Property Decorators
Applied to class properties. These are often used to record metadata about the property (e.g., for database mapping or validation).
Parameter Decorators
Applied to method parameters. Usually used in conjunction with class or method decorators to mark specific arguments for special handling.
Class Decorators
A class decorator is called when the class is defined, not when it is instantiated. It receives the constructor of the class as its only argument.
Example: Class Decorator
function Sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
console.log(`Class ${constructor.name} has been sealed.`);
}
@Sealed
class UserReport {
constructor(public title: string) {}
}
// Any attempt to extend or modify the prototype of UserReport will now fail at runtime.
- In this example, the
Sealeddecorator prevents the class and its prototype from being modified at runtime. This is a common pattern for security or preventing accidental overrides in large codebases.
new). In reality, the decorator runs only once when the script is loaded and the class is defined.
Method Decorators
A method decorator is incredibly useful for utility logic. It receives three parameters: the prototype of the class, the name of the method, and a PropertyDescriptor.
Example: Method Decorator (Performance Tracker)
function LogExecutionTime(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`${propertyKey} took ${(end - start).toFixed(2)}ms`);
return result;
};
}
class DataProcessor {
@LogExecutionTime
processHeavyData() {
// Simulate a heavy task
for(let i = 0; i < 1000000; i++) {}
}
}
const processor = new DataProcessor();
processor.processHeavyData(); // Output: processHeavyData took 1.45ms
- The
PropertyDescriptorallows us to intercept the original function, run our logic (timing), and then execute the original logic.
.apply(this, args) when overriding a method in a decorator to ensure the this context of the class instance is preserved.
Property Decorators
Property decorators are slightly different. They don't have access to the PropertyDescriptor and only receive the target object and the property name. They are mostly used to watch for property changes or to attach metadata.
Example: Property Decorator
function MaxValue(max: number) {
return function(target: any, propertyKey: string) {
let value: number;
const getter = function() {
return value;
};
const setter = function(newVal: number) {
if (newVal > max) {
console.warn(`Value ${newVal} exceeds max ${max}! Setting to max.`);
value = max;
} else {
value = newVal;
}
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class GameCharacter {
@MaxValue(100)
health: number = 50;
}
const hero = new GameCharacter();
hero.health = 150; // Output: Value 150 exceeds max 100! Setting to max.
console.log(hero.health); // 100
- Note: This example uses a Decorator Factory (explained below) to pass the
maxvalue.
Parameter Decorators
A parameter decorator is used to record information about a specific parameter in a method's signature. It receives the target, the method name, and the index of the parameter.
Example: Parameter Decorator
function Required(target: any, propertyKey: string, parameterIndex: number) {
console.log(`Checking parameter at index ${parameterIndex} in ${propertyKey}...`);
}
class UserService {
updateUser(@Required id: string, name: string) {
console.log(`Updating user ${id}`);
}
}
- On their own, parameter decorators usually just "mark" data. You typically use them with a method decorator that reads this metadata and performs validation before the method runs.
Decorator Factory
If you want to pass custom arguments to your decorator (like a configuration object or a string), you need a Decorator Factory. A factory is simply a function that returns the decorator function itself.
Example: Decorator Factory
function Role(requiredRole: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const user = { role: 'guest' }; // Imagine getting this from a session
if (user.role !== requiredRole) {
throw new Error("Unauthorized access!");
}
return originalMethod.apply(this, args);
};
};
}
class AdminPanel {
@Role('admin')
deleteUser() {
console.log("User deleted.");
}
}
- The
Role('admin')call returns the actual decorator function that TypeScript then applies to thedeleteUsermethod.
Summary
- Decorators are functions that allow you to declaratively add behavior to classes and their members.
- They help keep your code DRY (Don't Repeat Yourself) by moving repetitive logic (like logging or validation) into reusable decorators.
- You must enable
experimentalDecoratorsin yourtsconfig.jsonto use them. - Class, Method, Property, and Parameter decorators each serve different purposes and receive different arguments.
- Decorator Factories allow you to pass parameters to your decorators, making them dynamic and highly flexible.
- They are a staple of modern TypeScript frameworks, especially for building scalable enterprise applications.