Skip to content

Instantly share code, notes, and snippets.

@jarhoads
Last active January 31, 2021 03:57
Show Gist options
  • Save jarhoads/4e291d5ab209921c0c9bcb96b8089e0e to your computer and use it in GitHub Desktop.
Save jarhoads/4e291d5ab209921c0c9bcb96b8089e0e to your computer and use it in GitHub Desktop.
typescript decorators
// 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