Instantly share code, notes, and snippets.

Embed
What would you like to do?
TypeScript Decorators Examples
function logClass(target: any) {
// save a reference to the original constructor
var original = target;
// a utility function to generate instances of a class
function construct(constructor, args) {
var c : any = function () {
return constructor.apply(this, args);
}
c.prototype = constructor.prototype;
return new c();
}
// the new constructor behaviour
var f : any = function (...args) {
console.log("New: " + original.name);
return construct(original, args);
}
// copy prototype so intanceof operator still works
f.prototype = original.prototype;
// return new constructor (will override original)
return f;
}
@logClass
class Person {
public name: string;
public surname: string;
constructor(name : string, surname : string) {
this.name = name;
this.surname = surname;
}
}
var p = new Person("remo", "jansen");
@logClassWithArgs({ when : { name : "remo"} })
class Person {
public name: string;
// ...
}
function logClassWithArgs(filter: Object) {
return (target: Object) => {
// implement class decorator here, the class decorator
// will have access to the decorator arguments (filter)
// because they are stored in a closure
}
}
function log(...args : any[]) {
switch(args.length) {
case 1:
return logClass.apply(this, args);
case 2:
return logProperty.apply(this, args);
case 3:
if(typeof args[2] === "number") {
return logParameter.apply(this, args);
}
return logMethod.apply(this, args);
default:
throw new Error();
}
}
function logMethod(target, key, descriptor) {
// save a reference to the original method this way we keep the values currently in the
// descriptor and don't overwrite what another decorator might have done to the descriptor.
if(descriptor === undefined) {
descriptor = Object.getOwnPropertyDescriptor(target, key);
}
var originalMethod = descriptor.value;
//editing the descriptor/value parameter
descriptor.value = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i - 0] = arguments[_i];
}
var a = args.map(function (a) { return JSON.stringify(a); }).join();
// note usage of originalMethod here
var result = originalMethod.apply(this, args);
var r = JSON.stringify(result);
console.log("Call: " + key + "(" + a + ") => " + r);
return result;
};
// return edited descriptor as opposed to overwriting the descriptor
return descriptor;
}
class Person {
public name: string;
public surname: string;
constructor(name : string, surname : string) {
this.name = name;
this.surname = surname;
}
@logMethod
public saySomething(something : string, somethingElse : string) : string {
return this.name + " " + this.surname + " says: " + something + " " + somethingElse;
}
}
var p = new Person("remo", "jansen");
p.saySomething("I love playing", "halo");
function logParameter(target: any, key : string, index : number) {
var metadataKey = `__log_${key}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
}
else {
target[metadataKey] = [index];
}
}
function logMethod(target, key, descriptor) {
if(descriptor === undefined) {
descriptor = Object.getOwnPropertyDescriptor(target, key);
}
var originalMethod = descriptor.value;
//editing the descriptor/value parameter
descriptor.value = function (...args: any[]) {
var metadataKey = `__log_${key}_parameters`;
var indices = target[metadataKey];
if (Array.isArray(indices)) {
for (var i = 0; i < args.length; i++) {
if (indices.indexOf(i) !== -1) {
var arg = args[i];
var argStr = JSON.stringify(arg) || arg.toString();
console.log(`${key} arg[${i}]: ${argStr}`);
}
}
var result = originalMethod.apply(this, args);
return result;
}
else {
var a = args.map(a => (JSON.stringify(a) || a.toString())).join();
var result = originalMethod.apply(this, args);
var r = JSON.stringify(result);
console.log(`Call: ${key}(${a}) => ${r}`);
return result;
}
}
// return edited descriptor as opposed to overwriting the descriptor
return descriptor;
}
class Person {
public name: string;
public surname: string;
constructor(name : string, surname : string) {
this.name = name;
this.surname = surname;
}
@logMethod
public saySomething(@logParameter something : string, somethingElse : string) : string {
return this.name + " " + this.surname + " says: " + something + " " + somethingElse;
}
}
var p = new Person("remo", "jansen");
p.saySomething("I love playing", "halo");
function logProperty(target: any, key: string) {
// property value
var _val = this[key];
// property getter
var getter = function () {
console.log(`Get: ${key} => ${_val}`);
return _val;
};
// property setter
var setter = function (newVal) {
console.log(`Set: ${key} => ${newVal}`);
_val = newVal;
};
// Delete property.
if (delete this[key]) {
// Create new property with getter and setter
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
class Person {
@logProperty
public name: string;
public surname: string;
constructor(name : string, surname : string) {
this.name = name;
this.surname = surname;
}
}
var p = new Person("remo", "Jansen");
p.name = "Remo";
var n = p.name;
function logParamTypes(target : any, key : string) {
var types = Reflect.getMetadata("design:paramtypes", target, key);
var s = types.map(a => a.name).join();
console.log(`${key} param types: ${s}`);
}
class Foo {}
interface IFoo {}
class Demo{
@logParameters
doSomething(
param1 : string,
param2 : number,
param3 : Foo,
param4 : { test : string },
param5 : IFoo,
param6 : Function,
param7 : (a : number) => void,
) : number {
return 1
}
}
// doSomething param types: String, Number, Foo, Object, Object, Function, Function
@CaselIT

This comment has been minimized.

Copy link

CaselIT commented Jan 27, 2016

Thanks for the tutorials, I've tried them, but in typescript 1.7 the decoration for the methods does not work anymore. They now seem to have only 2 arguments.

@CaselIT

This comment has been minimized.

Copy link

CaselIT commented Jan 27, 2016

I've found the problem, I had to target es5 instead of the default es3
http://blogs.msdn.com/b/typescript/archive/2015/11/30/announcing-typescript-1-7.aspx?PageIndex=3#comments

@amcdnl

This comment has been minimized.

Copy link

amcdnl commented Jan 29, 2016

@remojansen I get unexpected ... when I'm trying to do it. see: https://gist.github.com/amcdnl/c3d43b4d52acedbdf6f1

@remojansen

This comment has been minimized.

Copy link
Owner

remojansen commented May 22, 2016

Hi guys sorry but the decorators signatures are a bit different since I wrote this. You can fins the new signatures here:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

I will try to update this in the future but right now I don't have time :( Feel free to fork and send a PR if you do!

@rogerdehe

This comment has been minimized.

Copy link

rogerdehe commented Jun 20, 2016

About the paramter decorator, I am confused:

  var metadataKey = `__log_${key}_parameters`;
  if (Array.isArray(target[metadataKey])) {
    target[metadataKey].push(index);
  }
  else {
    target[metadataKey] = [index];
  }

what do you mean by target[metadataKey], it is always undefined.

playground

@k1r0s

This comment has been minimized.

Copy link

k1r0s commented Jan 31, 2017

it seems that Typescript does not support async decorators yet.

If anyone is looking for decorators with async calls or transactions, please take a look here:

https://github.com/ciroreed/kaop/blob/easy-decorators/test/showcase.js

@dulowski-marek

This comment has been minimized.

Copy link

dulowski-marek commented Feb 26, 2017

Class decorators are broken when targeting ES2015. Upon decorated class' initialization it rises error

  Error: (SystemJS) Class constructor Person cannot be invoked without 'new'
  TypeError: Class constructor Person cannot be invoked without 'new'

My tsconfig.json file:

{
  "compilerOptions": {
    "target": "es2015",
    "module": "commonjs",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": [ "es2015", "dom" ],
    "baseUrl": ".",
    "paths": {
      "app:*": ["app/*"]
    }
  },
  "include": [
    "app/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

It seems to be a problem with native classes.

Any thoughts?

@starbeast

This comment has been minimized.

Copy link

starbeast commented May 30, 2017

Correct me if I'm wrong but it looks like @logProperty decorator defines property on prototype making things a bit wrong: as I can see running code from typescript playground this is a window object and then once you make several objects of type Person, changing the name for one of them will resolve in changing the name for all the others (since this property lives in prototype)?

@jbouzekri

This comment has been minimized.

Copy link

jbouzekri commented Sep 9, 2017

In class decorator, indeed instanceof works but a simple console.log of the resulting object displays:

c
    name: "remo"
    surname: "jansen"
    __proto__: Object

How to get back the name of the original class Person instead of c ?

@kristianmandrup

This comment has been minimized.

Copy link

kristianmandrup commented Jan 28, 2018

@dulowski-marek I'm having the same problem. Anyone knows of a resolution to this while still targeting ES5? Thanks.

@PandaWood

This comment has been minimized.

Copy link

PandaWood commented Feb 19, 2018

I quite often get the error Converting circular structure to JSON on this code - where it calls JSON.stringify() if the object passed has cyclical data structures (most DOM elements have this)

Not a simple problem to solve either - I wonder if we should be using stringify()
https://stackoverflow.com/questions/11616630/json-stringify-avoid-typeerror-converting-circular-structure-to-json

@CosminIonascu

This comment has been minimized.

Copy link

CosminIonascu commented May 24, 2018

Hi @kristianmandrup, @dulowski-marek, I am having the same problem. Does any of you found any solution?

@paulparton

This comment has been minimized.

Copy link

paulparton commented Jul 26, 2018

I got the property decorator to work

export function logProperty() {
  return (target: any, key: string) => {
    // property value
    let _val = this[key];

    // property getter
    function getter() {
      console.log(`Get: ${key} => ${_val}`);
      return _val;
    }

    // property setter
    function setter(newVal) {
      console.log(`Set: ${key} => ${newVal}`);
      _val = newVal;
    }

    // Delete property.
    if (delete this[key]) {
      // Create new property with getter and setter
      Object.defineProperty(target, key, {
        get: getter,
        set: setter,
        enumerable: true,
        configurable: true,
      });
    }
  };
}

export class MyClass {
  @logProperty()
  public name: string = 'ThatGuy';
}
@Master76

This comment has been minimized.

Copy link

Master76 commented Oct 10, 2018

Be of great help! Thanks for sharing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment