Last active
January 31, 2021 03:57
-
-
Save jarhoads/4e291d5ab209921c0c9bcb96b8089e0e to your computer and use it in GitHub Desktop.
typescript decorators
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Decorator Function | |
function logDec(target: any, key: string, descriptor: any) { | |
// square | |
console.log(key); | |
} | |
class Calculator { | |
// Using the decorator | |
@logDec | |
square(n: number) { | |
return n * n; | |
} | |
} | |
// Each kind of decorator requires a different function signature, | |
// as the decorator is provided with different parameters depending on the decorator use. | |
// This section will provide several practical examples that can be used as a starting point for your own decorators. | |
// Property decorator | |
// As well as passing the name of the method in the key parameter, | |
// a property descriptor is passed in the descriptor parameter. | |
// The property descriptor is an object that contains the original method and some metadata. | |
// The method itself is found within the value property of the descriptor. | |
// When a method decorator returns a value, the value will be used as the descriptor. | |
// This means you can choose to observe, modify, or replace the original method. | |
// In the case of the logging method decorator, the original method in the descriptor is wrapped with a logging function, | |
// which logs the fact the method was called as well as the arguments passed and the value returned. | |
// Each time the method is called, the information is logged. | |
function loggerFun(target: any, key: string, descriptor: any) { | |
const original = descriptor.value; | |
descriptor.value = function (...args: any[]) { | |
// Call the original method | |
const result = original.apply(this, args); | |
// Log the call, and the result | |
console.log(`${key} with args ${JSON.stringify(args)} returned | |
${JSON.stringify(result)}`); | |
// Return the result | |
return result; | |
} | |
return descriptor; | |
} | |
class CalculatorLogged { | |
// Using the decorator | |
@loggerFun | |
square(num: number) { | |
return num * num; | |
} | |
} | |
const calculator = new CalculatorLogged(); | |
// square with args [2] returned 4 | |
calculator.square(2); | |
// square with args [3] returned 9 | |
calculator.square(3); | |
// Configurable Decorators | |
// You can make a decorator configurable by converting your decorator function into a decorator factory. | |
// A decorator factory is a function that returns a decorator function. | |
// The factory can have any number of parameters that can be used in the creation of the decorator. | |
function logFact(title: string) { | |
return (target: any, key: string, descriptor: any) => { | |
const original = descriptor.value; | |
descriptor.value = function (...args: any[]) { | |
// Call the original method | |
const result = original.apply(this, args); | |
// Log the call, and the result | |
console.log(`${title}.${key} | |
with args ${JSON.stringify(args)} | |
returned ${JSON.stringify(result)}`); | |
// Return the result | |
return result; | |
} | |
return descriptor; | |
}; | |
} | |
class CalculatorConfigured { | |
// Using the configurable decorator | |
@logFact('Calculator') | |
square(num: number) { | |
return num * num; | |
} | |
} | |
const calculatorFact = new CalculatorConfigured(); | |
// Calculator.square with args [2] returned 4 | |
calculatorFact.square(2); | |
// Calculator.square with args [3] returned 9 | |
calculatorFact.square(3); | |
// Class Decorators | |
// As with method decorators, you can choose to modify, wrap, or replace the constructor passed in the class decorator. | |
// When replacing the constructor, you must maintain the original prototype as this is not done automatically. | |
function logClass(target: any) { | |
const original = target; | |
// Wrap the constructor with a logging constructor | |
const constr: any = (...args) => { | |
console.log(`Creating new ${original.name}`); | |
const c: any = () => { | |
return original.apply(null, args); | |
} | |
c.prototype = original.prototype; | |
return new c(); | |
} | |
constr.prototype = original.prototype; | |
return constr; | |
} | |
@logClass | |
class CalculatorClassLogged { | |
square(n: number) { | |
return n * n; | |
} | |
} | |
// Creating new Calculator | |
var calc1 = new CalculatorClassLogged(); | |
// Creating new Calculator | |
var calc2 = new CalculatorClassLogged(); | |
// Property Decorators | |
// Property decorators can be split into a number of parts. | |
// both the getter and the setter are replaced with logging implementations. | |
// To do this, the original property is deleted before the replacements are added with the original name. | |
function logProp(target: any, key: string) { | |
let value = target[key]; | |
// Replacement getter | |
const getter = function () { | |
console.log(`Getter for ${key} returned ${value}`); | |
return value; | |
}; | |
// Replacement setter | |
const setter = function (newVal: any) { | |
console.log(`Set ${key} to ${newVal}`); | |
value = newVal; | |
}; | |
// Replace the property | |
if (delete this[key]) { | |
Object.defineProperty(target, key, { | |
get: getter, | |
set: setter, | |
enumerable: true, | |
configurable: true | |
}); | |
} | |
} | |
class CalculatorProp { | |
@logProp | |
public num: number = 0; | |
square() { | |
return this.num * this.num; | |
} | |
} | |
const calc = new CalculatorProp(); | |
// Set num to 4 | |
calc.num = 4; | |
// Getter for num returned 4 | |
// Getter for num returned 4 | |
calc.square(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment