Introduction
Unpack the power of TypeScript decorators and streamline your coding process by mastering decorator factories, an advanced feature that enhances meta-programming capabilities.
What are Decorators?
Decorators are a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. In TypeScript, decorators provide a way to add annotations and a meta-programming syntax for class declarations and members. Decorators are a stage 2 proposal for JavaScript and are available as an experimental feature of TypeScript.
Here is a simple decorator example:
function SimpleDecorator(constructor: Function) {
console.log('SimpleDecorator called');
}
@SimpleDecorator
class MyClass {
constructor() {
console.log('MyClass created');
}
}
What are Decorator Factories?
Decorator factories in TypeScript allow for customization of decorator functions. They are simply functions that return the expression that will be called by the decorator at runtime.
A basic decorator factory example:
function Color(value: string) {
return function(target) {
// This is the decorator
Object.defineProperty(target, 'color', {
value,
writable: false
});
};
}
@Color('blue')
class ColoredClass {}
console.log((new ColoredClass() as any).color); // Outputs: 'blue'
Using Decorator Factories
Decorator factories can be used to customize how decorators are applied to classes, methods, accessors, properties, or parameters. By using decorator factories, you can pass parameters to your decorators and return different decorator functions depending on those parameters.
Class Decorators
function Component(selector: string) {
return function (constructor: Function) {
constructor.prototype.selector = selector;
};
}
@Component('app-component')
class AppComponent {}
Method Decorators
function Log(target: any, propertyName: string, propertyDesciptor: PropertyDescriptor): PropertyDescriptor {
const method = propertyDesciptor.value;
propertyDesciptor.value = function (...args: any[]) {
console.log(`Calling ${propertyName} with`, args);
return method.apply(this, args);
};
return propertyDesciptor;
}
Accessory Decorators
function Format(formatString: string) {
return function(target: any, propertyName: string, descriptor: PropertyDescriptor) {
let originalValue = descriptor.get;
descriptor.get = function () {
let result = originalValue.apply(this);
return `Formatted date: ${result.toLocaleDateString()}`;
};
};
}
Property Decorators
function Default(defaultValue: any) {
return function (target: any, propertyName: string) {
let value = defaultValue;
Object.defineProperty(target, propertyName, {
get: () => value,
set: (newValue) => { value = newValue; },
enumerable: true,
configurable: true
});
};
}
Parameter Decorators
function Print(target: Object, methodName: string, parameterIndex: number) {
console.log(`Print decorator for ${methodName} called on parameter index ${parameterIndex}`);
}
Decorators with Dependencies
Sometimes decorators depend on other decorators, you can compose them to enhance their behavior. Here’s an example:
function First() {
console.log('First Decorator Factory');
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('First Decorator called');
};
}
function Second() {
console.log('Second Decorator Factory');
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('Second Decorator called');
};
}
class ExampleClass {
@First()
@Second()
method() {}
}
In the above example, First will be executed before Second, even though it appears after Second in the code.
Conclusion
Decorator factories in TypeScript give tremendous power to add custom behavior to classes, methods, and more. They bolster the development process, allowing for a declarative and reusable code which is easier to read and maintain. As you continue to practice using decorators and decorator factories, you’ll discover new ways to optimize and enhance your TypeScript applications.