Follow me: Twitter
Typescript decorators are a powerful feature that allows developers to add additional behavior to class declarations, methods, and properties. In this tutorial, we'll explore what decorators are and how to use them in your Typescript projects.
What are decorators?
Decorators are functions that are called with a specific target
, property
, descriptor
, or parameter
as their arguments. They can be used to modify the behavior of the decorated item in various ways.
How to use decorators
To use a decorator in your Typescript code, you first need to enable the experimentalDecorators
compiler option in your tsconfig.json
file:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
Once this option is enabled, you can use decorators by placing the @
symbol followed by the decorator name before the item you want to decorate.
For example, here's how you can use the @sealed
decorator to seal a class, making it non-extendable:
@sealed
class MyClass {
// class implementation here
}
class AnotherClass extends MyClass {
// this will cause a compile-time error because MyClass is sealed
}
Creating your own decorators
In addition to using the built-in decorators, you can also create your own decorators by defining a function with the decorator signature.
Here's an example of a custom @log
decorator that logs the arguments and return value of a decorated method:
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Arguments: ${args}`);
const result = originalMethod.apply(this, args);
console.log(`Result: ${result}`);
return result;
};
return descriptor;
}
class MyClass {
@log
add(x: number, y: number): number {
return x + y;
}
}
const myClass = new MyClass();
myClass.add(1, 2); // will log "Arguments: 1,2" and "Result: 3"
To use the
log
decorator, it is first defined as a function that takes atarget
,propertyKey
, anddescriptor
as its arguments. Thetarget
argument represents the object containing the decorated method, thepropertyKey
argument represents the name of the method, and thedescriptor
argument represents the property descriptor of the method.
In this example, the log
decorator stores a reference to the original method in a variable called originalMethod
, and then replaces the method implementation with a new function that logs the arguments and result before calling the original method and returning its result.
Finally, the decorator returns the modified descriptor
object.
Real-world examples
Now that we've seen how decorators work, let's look at some real-world examples of how they can be used.
Caching
One common use case for decorators is caching the result of a method so that it doesn't have to be recomputed every time it's called. Here's an example of a @cache
decorator that does this:
function cache(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
let cache: any;
descriptor.value = function(...args: any[]) {
if (!cache) {
cache = originalMethod.apply(this, args);
}
return cache;
};
return descriptor;
}
class MyClass {
@cache
expensiveComputation(x: number): number {
// expensive computation here
}
}
const myClass = new MyClass();
myClass.expensiveComputation(1); // will compute the result
myClass.expensiveComputation(1); // will return the cached result
This code defines a cache
decorator that can be applied to methods to cache their results. When the decorated method is called, the decorator checks if a cache already exists for the result. If it does, the cached result is returned. If it doesn't, the method is called and its result is cached for future calls.
Validation
Decorators can also be used to validate the input of a method or the state of an object. Here's an example of a @validate
decorator that checks if the input of a method is a non-empty string:
function validate(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
if (typeof args[0] !== 'string' || !args[0]) {
throw new Error('Invalid input');
}
return originalMethod.apply(this, args);
};
return descriptor;
}
class MyClass {
@validate
processString(str: string) {
// process string here
}
}
const myClass = new MyClass();
myClass.processString('foo'); // valid input
myClass.processString(''); // will throw an error
In this example, the validate
decorator replaces the method implementation with a function that checks the input and throws an error if it is invalid. If the input is valid, the original method is called.
Conclusion
Decorators are a powerful feature of Typescript that allow developers to add additional behavior to class declarations, methods, and properties. They are useful for tasks such as caching, validation, and logging.
In this tutorial, we've seen how to use and create decorators in Typescript, and we've looked at some real-world examples of how they can be used. Whether you're new to decorators or an experienced developer, I hope this tutorial has provided you with a good understanding of how they work and how you can use them in your projects.
Follow me: Twitter
Top comments (0)