07
DecDecorators in TypeScript
Understanding Decorators in TypeScript
Decorators in TypeScript Before learning this topic let's see what is TypeScript first, TypeScript, a superset of JavaScript, introduces several powerful features that enhance the language's capabilities. Among these, decorators stand out as a powerful tool for adding annotations and modifying behavior in a declarative manner. Inspired by languages like Python and Java, decorators provide a way to meta-program, allowing developers to write cleaner and more maintainable code.
So in this TypeScript Tutorial, We will Understand Decorators in TypeScript including types of decorators in Typescript.
What Are Decorators?
Decorators are special declarations that can be attached to classes, methods, accessors, properties, or parameters. They provide a way to add metadata, modify behavior, or enhance the functionality of the target they are applied to. Essentially, decorators are functions that receive specific arguments based on their target and can perform actions such as:
- Modifying the class or method definition.
- Adding new properties or methods.
- Wrapping or replacing existing methods.
- Adding metadata for dependency injection or validation.
Decorators help in adhering to the DRY (Don't Repeat Yourself) principle by abstracting repetitive patterns and enabling more declarative code structures.
Enabling Decorators in TypeScript
Before using decorators in TypeScript, you need to enable them in your project's configuration. Decorators are still an experimental feature and require explicit activation.
1. Update tsconfig.json
:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true, // Optional: Useful for frameworks like Angular
// ... other options
}
}
2. Install Necessary Dependencies:
Some decorator functionalities, especially those relying on metadata, might require additional libraries like reflect-metadata
.
npm install reflect-metadata --save
Then, import it at the entry point of your application:
import "reflect-metadata";
Types of Decorators
TypeScript supports several types of decorators, each targeting different parts of a class or its members.
1. Class Decorators
Definition: A decorator applied to a class declaration. It can observe, modify, or replace the class definition.
Signature:
function classDecorator<TFunction extends Function>(target: TFunction): TFunction | void
Example:
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return `Hello, ${this.greeting}`;
}
}
2. Method Decorators
Definition: A decorator applied to a method declaration. It can modify the method's descriptor, enabling behavior like logging, validation, or access control.
Signature:
function methodDecorator(
target: Object,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void
Example:
function enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value;
};
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return `Hello, ${this.greeting}`;
}
}
3. Accessor Decorators
Definition: A decorator applied to a getter or setter. It allows modification of the property descriptor.
Signature:
function accessorDecorator(
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
): PropertyDescriptor | void
Example:
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}
class Person {
private _name: string;
constructor(name: string) {
this._name = name;
}
@configurable(false)
get name() {
return this._name;
}
}
4. Property Decorators
Definition: A decorator applied to a property declaration. Unlike method decorators, property decorators cannot directly modify property descriptors but can be used to observe or add metadata.
Signature:
function propertyDecorator(target: Object, propertyKey: string | symbol): void
Example:
function format(formatString: string) {
return function (target: any, propertyKey: string) {
// Metadata can be attached here using Reflect.metadata
Reflect.defineMetadata("format", formatString, target, propertyKey);
};
}
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
5. Parameter Decorators
Definition: A decorator applied to a method parameter. It can be used to inject dependencies or perform validations.
Signature:
function parameterDecorator(
target: Object,
propertyKey: string | symbol,
parameterIndex: number
): void
Example:
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
// Store metadata about required parameters
const existingRequiredParameters: number[] =
Reflect.getOwnMetadata("requiredParameters", target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata("requiredParameters", existingRequiredParameters, target, propertyKey);
}
class UserService {
greet(@required name: string) {
return `Hello, ${name}`;
}
}
Practical Examples
To better understand decorators, let's explore some practical use cases.
1. Logging with Method Decorators
Suppose you want to log every time a method is called along with its arguments. A method decorator can wrap the original method to achieve this.
function Log(
target: any,
propertyName: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Method ${propertyName} called with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyName} returned:`, result);
return result;
};
return descriptor;
}
class Calculator {
@Log
add(a: number, b: number): number {
return a + b;
}
@Log
multiply(a: number, b: number): number {
return a * b;
}
}
const calc = new Calculator();
calc.add(2, 3);
// Console Output:
// Method add called with args: [2, 3]
// Method add returned: 5
calc.multiply(4, 5);
// Console Output:
// Method multiply called with args: [4, 5]
// Method multiply returned: 20
2. Singleton with Class Decorators
Ensure a class has only one instance by using a class decorator.
function Singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
private static instance: any;
constructor(...args: any[]) {
if ((constructor).instance) {
return (constructor).instance;
}
super(...args);
(constructor).instance = this;
}
};
}
@Singleton
class Database {
connection: string;
constructor() {
this.connection = "Database connection established";
}
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
Use Cases
Decorators are versatile and can be applied in various scenarios, including but not limited to:
- Dependency Injection: Frameworks like Angular use decorators to inject dependencies into classes.
- Validation: Automatically validate method parameters or properties based on decorators.
- Logging and Monitoring: As shown in the logging example, decorators can monitor method calls.
- Authorization: Restrict access to methods based on user roles or permissions.
- Metadata Management: Store and retrieve metadata for classes and their members, useful in ORM libraries or serialization.
Conclusion
Decorators in TypeScript provide a declarative way to enhance and modify classes and their members. By abstracting repetitive patterns and enabling meta-programming, decorators contribute to cleaner and more maintainable codebases. While still an experimental feature, their integration into TypeScript opens doors to more expressive and powerful coding paradigms, especially when building large-scale applications or frameworks. As you explore decorators, remember to keep abreast of their evolution in the TypeScript and JavaScript ecosystems, as the standards around them continue to mature. With thoughtful application, decorators can significantly streamline your development workflow and code architecture.
FAQs
Take our Typescript skill challenge to evaluate yourself!
In less than 5 minutes, with our skill challenge, you can identify your knowledge gaps and strengths in a given skill.