Decorators are used to modify classes and its members (methods, properties).
The question may be, why do we need decorators to change those things? Why not directly change in the declarations?
Directly changing is not scalable. If you need the same changes in 100 classes, you have to write it 100 times. Using decorators, you write it once and apply it as many times as you want.
Decorators give you a central point of change which easier to work with. Frameworks like NestJS
, Angular
use decorators extensively.
In TypeScript, you can decorate the following things:
- class
- method
- property
- accessor
- parameter
Decorators are a TypeScript feature and are not officially included in JavaScript yet. However, they are likely to be added in the future (Currently in stage 2).
Class decorator
// The following decorator makes any class more Funky
function makeFunky(target: Function) {
return class extends target {
constructor() {
super()
}
funkIt() {
console.log("Get The Funk Outta My Face")
}
}
}
}
@makeFunky
class Car {
wheels = 4
state = 'pause'
drive() {
this.state = 'running'
}
}
const car = new Car()
car.funkIt()
// logs: Get The Funk Outta My Face
Decorators are just functions, makeFunky
is the decorator above. makeFunky
gets the class Car
it's applied to as a parameter. It returns a new class which is just a modified version of the original class.
Decorators are called when the class is declared—not when an object is instantiated.
Method decorator
Decorators applied to a method gets the following parameters
-
target : for static methods,
target
is the constructor function of the class. For an instance method,target
is the prototype of the class. - propertyKey : The name of the method.
- descriptor : The Property Descriptor for the method
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value
descriptor.value = function () {
console.log('arguments are ', arguments)
const result = originalMethod.apply(this, arguments)
console.log(`result is ${result}`)
return result
}
}
class Calculator {
@log
add(x: number, y: number): number {
return x + y
}
}
const calc = new Calculator()
calc.add(10, 20)
/*
[LOG]: "arguments are ", {
"0": 10,
"1": 20
}
[LOG]: "result is 30"
*/
Property decorator
Similar to method decorator. Gets the same parameters target
, propertyKey
, and descriptor
as the method decorators. Its return value is ignored.
function logMutation(target: any, key: string ) {
var _val = target[key]
Object.defineProperty(target, key, {
set: function(newVal) {
console.log('new value is ' + newVal)
_val = newVal
},
get: function() {
return _val
}
})
}
class Person {
@logMutation
public age: number
constructor(age: number) {
this.age = age
}
}
const jack = new Person(20)
jack.age = 40
/*
[LOG]: "new value is 20"
[LOG]: "new value is 40"
*/
Accessor and parameter decorators
Accessor decorators are same as method decorators, but they are applied to either the getter or setter method of a single member.
Parameter decorators are applied on, you got it, parameters.
Decorator Factory
A common pattern in decorator usage is calling a function that returns a decorator. Here's the previously mentioned method decorator log
, returned by invoking the function logger
function logger(functionName: string) {
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value
descriptor.value = function () {
console.log('method name: ' + functionName)
console.log('arguments are ', arguments)
const result = originalMethod.apply(this, arguments)
console.log(`result is ${result}`)
return result
}
}
return log
}
class Calculator {
@logger('add')
add(x: number, y: number): number {
return x + y
}
}
const calc = new Calculator()
calc.add(10, 20)
/*
[LOG]: "method name: add"
[LOG]: "arguments are ", {
"0": 10,
"1": 20
}
[LOG]: "result is 30"
*/
Decorator in the wild
Some use cases of decorators as being utilized by popular libraries/frameworks.
-
NestJS uses the class decorator
@Controller('pathName')
to define a class as a controller. Decorators associate classes with required metadata and enable Nest to create a routing map (tie requests to the corresponding controllers). It also uses decorators to define modules, injectable instances for its dependency injection system. -
TypeORM uses decorators to define a class as an entity
@Entity()
, tagging properties as columns@Column()
, auto-incremented ID etc. - The NPM package class-validator is used for validating properties in an object (used in validations such as income data, arguments of a function). It has decorators like
@IsInt()
@Min(5)
@Max(25)
to set restrictions on a particular field.
Top comments (0)