TS decorators:
TS decorators allow extra functionality to be added on a class. The class is altered by decorators at declare time, before any instance of the class is created.
Syntax:
Decorators are declared with an @
sign, for example @metadata
. TS will now search for a corresponding metadata function and will automatically supply it with sevaral argument which vary on what is exactly decorated (e.g. class or class property get different arguments)
These parameters are supplied in the decorator function:
- The prototype object of the class
- propertykey or method name
- PropertyDescriptor object, looks like this
{writable: true, enumerable: false, configurable: true, value: ƒ}
Depending on the type of decorator 1-3 of these arguments are passed to the decorator function.
Types of decorators:
The following decorators can be applied to a class and TS will evaluate them in the following order (following summation comes from TS docs):
- Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each instance member.
- Parameter Decorators, followed by Method, Accessor, or Property
Decorators are applied for each static member.
- Parameter Decorators are applied for the constructor.
- Class Decorators are applied for the class
The best way to understand them better is via examples. Note that these example do need significant understanding of the TS language and concepts like PropertyDescriptor
.
Method decorators:
function overwrite(
target: myClass,
propertyKey: string,
descriptor: PropertyDescriptor
) {
console.log('I get logged when the class is declared!')
// desciptor.value refers to the actual function fo the class
// we are changing it to another function which straight up
// overrides the other function
descriptor.value = function () {
return 'newValue method overwritten'
}
}
function enhance(
target: myClass,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const oldFunc = descriptor.value;
// desciptor.value refers to the actual function fo the class
// we are changing it to another function which calls the old
// function and does some extra stuff
descriptor.value = function (...args: any[]) {
console.log('log before');
const returnValue = oldFunc.apply(this, args)
console.log('log after');
return returnValue;
}
}
class myClass {
// here is the decorator applied
@overwrite
foo() {
return 'oldValue';
}
// here is the decorator applied
@enhance
bar() {
return 'oldValueBar';
}
}
const instance =new myClass()
console.log(instance.foo())
console.log(instance.bar())
// The following gets logged in this order:
//I get logged when the class is declared!
// newValue method overwritten
// log before
// log after
// oldValueBar
Property decorators:
function metaData(
target: myClass,
propertyKey: string,
// A Property Descriptor is not provided as an argument to a property decorator due to
// how property decorators are initialized in TypeScript.
) {
console.log('Execute your custom code here')
console.log(propertyKey)
}
class myClass {
@metaData
foo = 5
}
// The following gets logged in this order:
// Execute your custom code here
// foo
Class decorators (from TS docs):
function seal(
constructor: Function,
) {
// Object.seal() does the following:
// Prevents the modification of attributes of
// existing properties, and prevents the addition
// of new properties
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@seal
class myClass {
bar?: any;
foo = 5
}
myClass.prototype.bar = 10;
// The following error will be thrown:
// Uncaught TypeError: Cannot add property bar,
// object is not extensible
Decorators and decorator factories:
decorators can be declared via decorators function or decorator factory functions. There is a difference in syntax which is best explained via an example:
// Returns a decorator function, we can return any function
// based on argument if we want
function decoratorFactory(arg: string) {
return function decorator(
target: myClass,
propertyKey: string,
) {
console.log(`Log arg ${arg} in decorator factory`);
}
}
// Define a decorator function directly
function decorator(
target: myClass,
propertyKey: string,
) {
console.log('Standard argument');
}
class myClass {
// Note the parentheses and optional arguments
// in the decorator factory
@decoratorFactory('myArgument')
foo = 'foo';
// No parentheses or arguments
@decorator
bar = 'bar';
}
// The following gets logged in this order:
// Log arg myArgument in decorator factory
// Standard argument
@Injectable
into a decorator, refer – Anand Rockzz