- 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 Parameter Decorators
In TypeScript, parameter decorators are a specialized type of decorator used to observe or add metadata to individual function parameters. While they are less common than class or method decorators, they are incredibly powerful when building frameworks or libraries that require deep introspection of your code, such as those used for logging, validation, or dependency injection.
What is a Parameter Decorator?
A parameter decorator is a function declared just before a parameter declaration. It is applied to the parameters of a class method or a constructor. Unlike method decorators, a parameter decorator cannot directly change the value of the argument being passed; instead, it provides information about that parameter that can be used later by other decorators or the class itself.
Syntax of Parameter Decorators
The decorator function for parameters accepts three specific arguments:
function ParameterDecorator(target: any, methodName: string | symbol, parameterIndex: number) {
// Logic to register metadata or observe the parameter
}
target: Either the constructor function of the class (for a static member) or the prototype of the class (for an instance member).methodName: The name of the method the parameter belongs to. For a constructor, this isundefined.parameterIndex: The ordinal index of the parameter in the function’s argument list (starting at 0).
Enabling Parameter Decorators in TypeScript
By default, TypeScript does not enable decorator support. You must explicitly allow them in your configuration. Open your tsconfig.json file and ensure the following flag is set to true:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Applying a Simple Parameter Decorator
A simple parameter decorator can log details about how a class is structured. This is often used during development to debug method signatures or ensure that metadata is being correctly applied.
Example: Applying a Simple Parameter Decorator
function LogParameter(target: any, methodName: string, parameterIndex: number) {
console.log(`Analyzing: Method "${methodName}" has a parameter at index ${parameterIndex}`);
}
class UserSettings {
updateEmail(@LogParameter newEmail: string) {
console.log(`Email updated to: ${newEmail}`);
}
}
// Output when the code is loaded (not when called):
// Analyzing: Method "updateEmail" has a parameter at index 0
- The
@LogParameterdecorator runs as soon as the class is defined by the JavaScript engine, not when theupdateEmailmethod is actually executed.
Using Parameter Decorators for Validation
Validation is a common real-world use case. Because a parameter decorator cannot stop a function from running, we usually "mark" a parameter as required and then use a separate validator to check it.
Example: Parameter Decorator for Validation
import "reflect-metadata";
const REQUIRED_METADATA_KEY = "requiredParameters";
function Required(target: any, methodName: string, parameterIndex: number) {
let existingRequiredParameters: number[] = Reflect.getOwnMetadata(REQUIRED_METADATA_KEY, target, methodName) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(REQUIRED_METADATA_KEY, existingRequiredParameters, target, methodName);
}
function validate(target: any, methodName: string, args: any[]) {
let requiredParameters: number[] = Reflect.getOwnMetadata(REQUIRED_METADATA_KEY, target, methodName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= args.length || args[parameterIndex] === undefined) {
throw new Error(`Missing required argument at position ${parameterIndex} in ${methodName}`);
}
}
}
}
class BugTracker {
saveBug(@Required title: string) {
console.log(`Bug saved: ${title}`);
}
}
const tracker = new BugTracker();
// Manually calling validation (In frameworks, this is automated via method decorators)
validate(tracker, "saveBug", []); // Throws: Missing required argument at position 0 in saveBug
Parameter Decorators for Dependency Injection
If you have used frameworks like NestJS or Angular, you have seen parameter decorators used for Dependency Injection (DI). They tell the system which specific service should be "injected" into a class constructor.
Example: Dependency Injection with Parameter Decorators
function Inject(token: string) {
return function(target: any, methodName: string, parameterIndex: number) {
console.log(`Metadata: Parameter at index ${parameterIndex} needs service: ${token}`);
// The DI container would use this info to provide the correct instance
};
}
class DatabaseService {}
class AppController {
constructor(@Inject('DB_SERVICE') private db: DatabaseService) {
console.log('Controller initialized');
}
}
- The
@Injectdecorator acts as a "marker" that helps an external "Container" or "Injector" understand what the class needs to function.
Using Parameter Decorators with Reflect Metadata
The reflect-metadata library is the standard way to work with decorators in TypeScript. It provides a centralized registry for storing information about classes and their members.
Example: Using Reflect Metadata with Parameter Decorators
import "reflect-metadata";
function Role(roleName: string) {
return function(target: any, methodName: string, parameterIndex: number) {
const roles = Reflect.getOwnMetadata("roles", target, methodName) || {};
roles[parameterIndex] = roleName;
Reflect.defineMetadata("roles", roles, target, methodName);
};
}
class AdminPanel {
deleteUser(@Role("admin") userId: string) {
console.log(`User ${userId} deleted.`);
}
}
// Checking metadata later
const meta = Reflect.getOwnMetadata("roles", AdminPanel.prototype, "deleteUser");
console.log(meta); // Output: { '0': 'admin' }
reflect-metadata, you must import it once at the entry point of your application (like index.ts or main.ts).
Parameter Decorators with Custom Logic
While parameter decorators don't have the power to change the method's behavior on their own, you can get creative by modifying the method's prototype inside the decorator. However, this is generally considered advanced and should be used with caution.
Example: Parameter Decorator with Custom Logic
function AutoUpper(target: any, methodName: string, parameterIndex: number) {
const originalMethod = target[methodName];
// Replacing the original method with a wrapper that modifies arguments
target[methodName] = function(...args: any[]) {
if (typeof args[parameterIndex] === "string") {
args[parameterIndex] = args[parameterIndex].toUpperCase();
}
return originalMethod.apply(this, args);
};
}
class Logger {
log(@AutoUpper message: string) {
console.log(`LOG: ${message}`);
}
}
const myLogger = new Logger();
myLogger.log("check database connection"); // Output: LOG: CHECK DATABASE CONNECTION
target[methodName] inside a parameter decorator can lead to issues if multiple parameter decorators are applied to the same method. A more robust way is to use a Method Decorator to handle argument manipulation.
Summary
- Purpose: Parameter decorators are used to "tag" or add metadata to method arguments.
- Execution: They run at class definition time, not at runtime when the method is called.
- Parameters: They receive the
targetprototype,methodName, andparameterIndex. - Ecosystem: They are fundamental for building Dependency Injection and Validation systems in modern TypeScript frameworks.
- Tooling: Use the
reflect-metadatalibrary to maximize the utility of these decorators.