"this" context in inner anonymous objects (a.k.a. "this" makes me crazy)

Topics: Language Specification
Nov 12, 2013 at 1:42 PM
Edited Nov 12, 2013 at 1:44 PM
I run into an issue, which i cannot resolve. I use knockout.js and want to declare a computed observable, which is able to read/write. Therefore you need to provide a getter and a setter, like this:
public class Something{
constructor(private data:any){ }

        public amount = ko.computed<number>({
            read: () => {
                return Number(this.data);
            },
            write: (value) => {
                this.data = Number(value);
            }
        });
The problem is, that "this.data" is always undefined. With VS debugging i realized, that "this" is bound to the window context.

Therefore i tried to introduce a "self" variable, which result in compile error: "Could not find symbol '_self'":
public class Something{
constructor(public data:any){ }
private _self = this;

        public amount = ko.computed<number>({
            read: () => {
                return Number(_self.data);
            },
            write: (value) => {
                _self.data = Number(value);
            }
        });
What is the correct way of resolving this issue? I know, there is one possibility to use "bind", but i cannot see, if and how this will help me here...
Coordinator
Nov 12, 2013 at 5:06 PM
When I compile what you wrote here (removing 'public' from the class declaration), I get this, which correctly captures 'this' into _this. Maybe you can post the compiled output of the code you're working with?
var Something = (function () {
    function Something(data) {
        var _this = this;
        this.data = data;
        this.amount = ko.computed({
            read: function () {
                return Number(_this.data);
            },
            write: function (value) {
                _this.data = Number(value);
            }
        });
    }
    return Something;
})();
Nov 12, 2013 at 7:28 PM
Edited Nov 12, 2013 at 7:56 PM
We face a similar problem in our application, and there is actually a simple solution: define all computed observables inside the constructor.
So in your case you just have to write this:
public class Something {
    public amount: ko.Computed<number>; // or KnockoutComputed<number>, depending on your definition file

    constructor(private data:any) { 
        this.amount = ko.computed<number>({
            read: () => {
                return Number(this.data);
            },
            write: (value) => {
                this.data = Number(value);
            }
        });
    }
}
When writing classes you should never use this outside class methods or constructors.
Nov 13, 2013 at 12:12 PM
Thanks for the feedback. Probably it is not a TypeSCript issue, because i found in the documentation, that you have to declare the "this" context via a "owner" attribute:

http://knockoutjs.com/documentation/computedObservables.html#computed_observable_reference

Anyway that your solution is also a valid approach. Thanks!
Nov 13, 2013 at 3:51 PM
@humbrie, if you specify lambda functions for your read and write properties then the value of "owner" will have no impact.
// KO does this
  options.read.call(options.owner);

// The read function looks like this:
read: function () {
    return Number(_this.data);  // i.e. real "this" is not involved
 }
I agree with @RyanCavanaugh that there is nothing wrong with your original code.

However, I have noticed that on some rare occasions the compiler fails to write out var _this = this; I have not been able to repro this because usually on the next compilation the code appears to have rectified itself miraculously.

What does your compiled JS look like?
Nov 14, 2013 at 10:00 AM
well, the compiled code seems correct (similar like jpatte´s). I ended up in this solution, which now seems to work (i´ve omitted other constructor params here):
class ObservableAction {
       
        constructor(data: any = 1) {          
            if (data) this.data(data);
        }

        public data = ko.observable();

        public amount = ko.computed({
            read: () => {
                return Number(this.data());
            },
            write: (value) => {
                this.data(value);
            },
            owner: this
        });
which compiles to:
 var ObservableAction = (function () {
        function ObservableAction(data) {
            if (typeof data === "undefined") { data = 1; }
            var _this = this;
            
            this.data = ko.observable();
            
            this.amount = ko.computed({
                read: function () {
                    return Number(_this.data());
                },
                write: function (value) {
                    _this.data(value);
                },
                owner: this
            });

             if (data)
                this.data(data);
In the end there is no big difference... I just use another "data" observable (which i dont need for binding).
I assume, that visual studio has given me a false debug information about "this".
I use internet explorer to debug typescript in visual studio.