Decorators
## Learning Objectives
- Understand decorator concept
- Use class decorators
- Apply method decorators
- Work with property decorators
- Configure TypeScript for decorators
## Overview
Decorators are an experimental feature (stage 2) that allow you to modify classes and their members. They provide a way to add metadata or change behavior.
## Enabling Decorators
### tsconfig.json
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```
## Class Decorators
### Basic Syntax
```typescript
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
```
### Decorator Factories
```typescript
function color(value: string) {
return function (constructor: Function) {
constructor.prototype.color = value;
};
}
@color("blue")
class Car {
brand: string;
}
console.log((new Car() as any).color); // "blue"
```
## Method Decorators
### Accessor Decorators
```typescript
function readonly(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.writable = false;
}
class Person {
@readonly
name(): string {
return "Alice";
}
}
let p = new Person();
// p.name = () => "Bob"; // Error: Cannot assign
```
### Method Decorator
```typescript
function log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with`, args);
return original.apply(this, args);
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
let calc = new Calculator();
calc.add(2, 3);
// Calling add with [2, 3]
// 5
```
## Property Decorators
```typescript
function defaultValue(value: any) {
return function (
target: any,
propertyKey: string
) {
target[propertyKey] = value;
};
}
class Config {
@defaultValue("localhost")
host: string;
}
let config = new Config();
console.log(config.host); // "localhost"
```
## Parameter Decorators
```typescript
function required(
target: any,
propertyKey: string | symbol,
parameterIndex: number
) {
// Mark parameter as required
}
function validate(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
// Validate required parameters
return original.apply(this, args);
};
}
class UserService {
create(
@required name: string,
@required age: number
): User {
return { name, age };
}
}
```
## Decorator Composition
### Multiple Decorators
```typescript
function first() {
console.log("first(): evaluated");
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("first(): called");
};
}
function second() {
console.log("second(): evaluated");
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log("second(): called");
};
}
class Example {
@first()
@second()
method() {}
}
// Output:
// first(): evaluated
// second(): evaluated
// second(): called
// first(): called
```
## Class Method Decorators
### Logging Decorator
```typescript
function logExecution(label: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
const start = Date.now();
const result = original.apply(this, args);
const duration = Date.now() - start;
console.log(`${label} took ${duration}ms`);
return result;
};
};
}
class UserService {
@logExecution("getUser")
getUser(id: number) {
// expensive operation
return { id, name: "Alice" };
}
}
```
## Property Descriptor
### Available Properties
```typescript
{
value?: any,
writable?: boolean,
enumerable?: boolean,
configurable?: boolean,
get?: () => any,
set?: (v: any) => void
}
```
## Reflect Metadata
### Installation
```bash
npm install reflect-metadata
```
### Example
```typescript
import "reflect-metadata";
function required(
target: Object,
propertyKey: string,
parameterIndex: number
) {
const requiredParams: number[] =
Reflect.getMetadata("required", target, propertyKey) || [];
requiredParams.push(parameterIndex);
Reflect.defineMetadata(
"required",
requiredParams,
target,
propertyKey
);
}
class Person {
greet(@required name: string): string {
return `Hello, ${name}`;
}
}
```
## Common Use Cases
### Singleton Pattern
```typescript
function singleton(
constructor: T
) {
let instance: T;
return class extends constructor {
constructor(...args: any[]) {
if (instance) {
return instance;
}
instance = this as T;
super(...args);
}
};
}
@singleton
class Database {
constructor() {
console.log("Database connected");
}
}
let db1 = new Database();
let db2 = new Database();
console.log(db1 === db2); // true
```
### Auto-bind
```typescript
function autoBind(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
return {
configurable: true,
enumerable: false,
get() {
return original.bind(this);
}
};
}
class Component {
@autoBind
render() {
console.log(this);
}
}
```
## Limitations
### Experimental
- Not part of official ECMAScript yet
- API may change
- Use with caution in production
### Order of Execution
- Decorators evaluated top to bottom
- Decorators called bottom to top
- Be aware when combining multiple decorators
## Summary
- Decorators are experimental in TypeScript
- Enable with `experimentalDecorators`
- Class decorators receive constructor
- Method/accessor decorators receive descriptor
- Property decorators receive prototype
- Parameter decorators receive index
- Decorator factories return decorator functions
- Multiple decorators compose with evaluation/call order
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →