TypeScript Decorators – Questions & Answers

1. What are decorators in TypeScript?

Decorators: A TypeScript feature (borrowed from JavaScript) that allows you to attach metadata or modify the behavior of classes, methods, accessors, properties, or parameters at design time.

How do you enable decorators in TypeScript?

Add the following to tsconfig.json:


{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true }
} 

Note:emitDecoratorMetadata is needed for metadata-related decorators (e.g., with reflect-metadata).

2. What are the different types of decorators in TypeScript?

Class Decorators: Applied to a class constructor, used to modify or annotate the entire class.

Method Decorators: Applied to methods, used to modify behavior or add metadata.

Accessor Decorators: Applied to getters/setters, used to control property access.

Property Decorators: Applied to class properties, used to annotate or validate.

Parameter Decorators: Applied to method parameters, used for metadata or validation.

Can you give examples of each decorator type?


// Class, Method, Accessor, Property, and Parameter Decorators
// Class Decorator
function LogClass(constructor: Function) { console.log(`Class created: ${constructor.name}`);
} // Method Decorator
function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`Calling ${propertyKey} with args: ${args}`); return originalMethod.apply(this, args); };
} // Accessor Decorator
function ReadOnly(target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.set = undefined; // Prevent setting
} // Property Decorator
function Required(target: any, propertyKey: string) { // Store metadata (simplified example) console.log(`Property ${propertyKey} is required`);
} // Parameter Decorator
function LogParam(target: any, propertyKey: string, parameterIndex: number) { console.log(`Parameter ${parameterIndex} in ${propertyKey} is being decorated`);
} @LogClass
class User { @Required name: string; private _age: number; constructor(name: string, age: number) { this.name = name; this._age = age; } @ReadOnly get age(): number { return this._age; } @LogMethod greet(@LogParam message: string): string { return `Hello, ${this.name}! ${message}`; }
} // Testing decorators
const user = new User("kristal", 30);
console.log(user.greet("Welcome!"));
console.log(user.age); // Getter works
// user.age = 40; // Error: setter is undefined 

Output:


Class created: User
Property name is required
Parameter 0 in greet is being decorated
Calling greet with args: Welcome!
Hello, kristal! Welcome!
30 

3. How are decorators used for metadata and dependency injection in TypeScript?

Metadata: Decorators can attach metadata to classes, methods, or properties using the reflect-metadata library, enabling runtime introspection.

Dependency Injection (DI): Decorators annotate classes or properties to manage dependencies, often with an inversion-of-control (IoC) container.

Library:reflect-metadata is commonly used for metadata storage/retrieval.

Can you give an example of decorators for metadata and dependency injection?

Setup: Install reflect-metadata:


npm install reflect-metadata 

// Metadata and Dependency Injection with Decorators
import "reflect-metadata"; // Simple DI container
const container = new Map(); function Injectable(key: string) { return function (constructor: Function) { container.set(key, new constructor()); };
} function Inject(key: string) { return function (target: any, propertyKey: string) { Reflect.defineMetadata("inject:key", key, target, propertyKey); };
} function LogMetadata(target: any, propertyKey: string, parameterIndex: number) { Reflect.defineMetadata("param:log", true, target, propertyKey);
} // Service
@Injectable("logger")
class LoggerService { log(message: string): void { console.log(`Log: ${message}`); }
} // Consumer class
class App { @Inject("logger") private logger: LoggerService; constructor() { // Resolve dependency const key = Reflect.getMetadata("inject:key", this, "logger"); this.logger = container.get(key); } @LogMetadata run(@LogMetadata message: string): void { const hasLog = Reflect.getMetadata("param:log", this, "run"); if (hasLog) { this.logger.log(message); } }
} // Testing
const app = new App();
app.run("Test message"); 

Output:


Log: Test message 

4. Can you provide a comprehensive example of TypeScript decorators?

Project Structure:


ts-decorators/
├── src/
│ └── main.ts
├── tsconfig.json
└── package.json 

main.ts:


// Comprehensive Decorator Example
import "reflect-metadata"; // DI Container
const container = new Map(); // Class Decorator
function Injectable(key: string) { return function (constructor: Function) { console.log(`Registering ${constructor.name} with key: ${key}`); container.set(key, new constructor()); };
} // Property Decorator for DI
function Inject(key: string) { return function (target: any, propertyKey: string) { Reflect.defineMetadata("inject:key", key, target, propertyKey); };
} // Method Decorator for Logging
function LogExecution(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { console.log(`Executing ${propertyKey} with args: ${args}`); const result = await originalMethod.apply(this, args); console.log(`Result of ${propertyKey}: ${result}`); return result; };
} // Accessor Decorator
function ValidateAge(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalSet = descriptor.set; descriptor.set = function (value: number) { if (value < 0) throw new Error("Age cannot be negative"); originalSet!.call(this, value); };
} // Parameter Decorator
function RequiredParam(target: any, propertyKey: string, parameterIndex: number) { const existing = Reflect.getMetadata("required:params", target, propertyKey) || []; existing.push(parameterIndex); Reflect.defineMetadata("required:params", existing, target, propertyKey);
} // Service
@Injectable("logger")
class LoggerService { log(message: string): void { console.log(`Log: ${message}`); }
} // Main Class
@Injectable("user")
class User { @Inject("logger") private logger: LoggerService; private _age: number; constructor() { const key = Reflect.getMetadata("inject:key", this, "logger"); this.logger = container.get(key); } @ValidateAge set age(value: number) { this._age = value; } get age(): number { return this._age; } @LogExecution greet(@RequiredParam name: string): string { const requiredParams = Reflect.getMetadata("required:params", this, "greet") || []; if (requiredParams.includes(0) && !name) { throw new Error("Name is required"); } this.logger.log(`Greeting ${name}`); return `Hello, ${name}!`; }
} // Testing
try { const user = new User(); user.age = 30; console.log(user.greet("kristal")); // user.greet(""); // Throws error // user.age = -5; // Throws error
} catch (e) { console.error(`Error: ${e.message}`);
} 

package.json:


{ "name": "ts-decorators", "version": "1.0.0", "scripts": { "start": "tsc && node dist/main.js", "build": "tsc", "watch": "tsc --watch" }, "dependencies": { "reflect-metadata": "^0.2.2" }, "devDependencies": { "typescript": "^5.6.2" }
} 

tsconfig.json:


{ "compilerOptions": { "target": "ESNext", "module": "ESNext", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "experimentalDecorators": true, "emitDecoratorMetadata": true }, "include": ["src/**/*"], "exclude": ["node_modules"]
} 

Steps to Run:

  1. Create the project structure.
  2. Install dependencies: npm install.
  3. Compile: npm run build.
  4. Run: npm start.

Output:


Registering LoggerService with key: logger
Registering User with key: user
Executing greet with args: kristal
Log: Greeting kristal
Result of greet: Hello, kristal!
Hello, kristal! 

Description:

5. What are common mistakes in TypeScript decorators?

6. What are best practices for TypeScript decorators?

  1. General:
    • Enable experimentalDecorators and emitDecoratorMetadata in tsconfig.json.
    • Use reflect-metadata for metadata-driven applications.
  2. Class Decorators:
    • Use for class-level metadata or DI registration.
    • Return a new constructor only when modifying the class.
  3. Method/Accessor Decorators:
    • Preserve original method behavior with descriptor.value.
    • Handle async methods with async/await in decorators.
  4. Property Decorators:
    • Use for metadata or lightweight validation; prefer getters/setters for complex logic.
    • Store metadata with Reflect.defineMetadata.
  5. Parameter Decorators:
    • Combine with method decorators for validation or logic.
    • Use metadata to track parameter requirements.
  6. Metadata/DI:
    • Use a robust DI container for dependency management.
    • Validate metadata presence with Reflect.getMetadata.
  7. General:
    • Follow TypeScript naming conventions (e.g., CamelCase for decorators).
    • Use tsc with strict mode (strict: true in tsconfig.json).
    • Document decorators with JSDoc or comments for clarity.
    • Test with edge cases (e.g., missing metadata, invalid inputs).
    • Use linters (e.g., eslint) with TypeScript plugins for consistency.