9

I have read "How to implement a typescript decorator?" and multiple sources but there is something that i have nor been able to do with decorators.

class FooBar {
    public foo(arg): void { 
        console.log(this);
        this.bar(arg);
    }
    private bar(arg) : void { 
        console.log(this, "bar", arg);
    }
}

If we invoke the function foo:

var foobar = new FooBar();
foobar.foo("test"); 

The object FooBar is logged in the console by console.log(this); in foo

The string "FooBar {foo: function, bar: function} bar test" is logged in the console by console.log(this, "bar", arg); in bar.

Now let's use a decorator:

function log(target: Function, key: string, value: any) {
    return {
        value: (...args: any[]) => {
            var a = args.map(a => JSON.stringify(a)).join();
            var result = value.value.apply(this, args); // How to avoid hard coded this?
            var r = JSON.stringify(result);
            console.log(`Call: ${key}(${a}) => ${r}`);
            return result;
        }
    };
}

We use the same function but decorated:

class FooBar {
    @log
    public foo(arg): void { 
        console.log(this);
        this.bar(arg);
    }
    @log
    private bar(arg) : void { 
        console.log(this, "bar", arg);
    }
}

And we invoke foo as we did before:

var foobarFoo = new FooBar();
foobarFooBar.foo("test");

The objectWindow is logged in the console by console.log(this); in foo

And bar is never invoked by foo because this.bar(arg); causes Uncaught TypeError: this.bar is not a function.

The problem is the hardcoded this inside the log decorator:

value.value.apply(this, args);

How can I conserve the original this value?

Community
  • 1
  • 1
Remo H. Jansen
  • 23,172
  • 11
  • 70
  • 93

2 Answers2

18

Don't use an arrow function. Use a function expression:

function log(target: Object, key: string, value: any) {
    return {
        value: function(...args: any[]) {
            var a = args.map(a => JSON.stringify(a)).join();
            var result = value.value.apply(this, args);
            var r = JSON.stringify(result);
            console.log(`Call: ${key}(${a}) => ${r}`);
            return result;
        }
    };
}

That way it will use the function's this context instead of the value of this when log is called.


By the way, I would recommend editing the descriptor/value parameter and return that instead of overwriting it by returning a new descriptor. That way you keep the properties currently in the descriptor and won't overwrite what another decorator might have done to the descriptor:

function log(target: Object, key: string, descriptor: TypedPropertyDescriptor<any>) {
    var originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
        var a = args.map(a => JSON.stringify(a)).join();
        var result = originalMethod.apply(this, args);
        var r = JSON.stringify(result);
        console.log(`Call: ${key}(${a}) => ${r}`);
        return result;
    };

    return descriptor;
}

More details in this answer - See the "Bad vs Good" example under "Example - Without Arguments > Notes"

David Sherret
  • 101,669
  • 28
  • 188
  • 178
  • 1
    Thanks for the answer I didn't notice that I was using an arrow function. – Remo H. Jansen May 19 '15 at 16:05
  • 1
    @OweRReLoaDeD yeah, very easy thing to miss. I didn't notice until I popped it in playground and saw the javascript doing `_this` – David Sherret May 19 '15 at 16:10
  • 4
    The OP's code appears to be from my blog post - https://smellegantcode.wordpress.com/2015/04/02/typescript-1-5-get-the-decorators-in/ - I'll update it! :) – Daniel Earwicker May 19 '15 at 19:32
  • I also had this issue son a blog post updated contetn at http://blog.wolksoftware.com/decorators-reflection-javascript-typescript – Remo H. Jansen May 20 '15 at 11:21
  • Nice example! one nitpick: seems the type of `target` should be `Object` not `Function`, since it's the prototype of the Class. – Janny Hou Sep 15 '17 at 17:21
  • @JannyHou yeah it should be. I had just copied and pasted from the question without looking at it in detail. I'll update it. Thanks! – David Sherret Sep 15 '17 at 17:35
0

I believe you can use

var self = this;

in order to preserve the 'this' at that specific point. Then, just use self at the later point where you would have wanted that particular this

Glen Despaux Jr
  • 1,064
  • 7
  • 19