Oliver
Oliver

Reputation: 36393

Scoping of 'this' in TypeScript

I have a very simple class, but already run into pain with the definition of ‘this’ in Typescript:

Typescript

/// <reference path='jquery.d.ts' />
/// <reference path='bootstrap.d.ts' />

module Problem {
    export class Index {
        detailsUrl: string;
        constructor() {
            $('.problem-detail-button').click((e) => {

                e.preventDefault();

                var $row = $(this).closest('tr'); //this must be that of the callback
                var problemId: number = $row.data('problem-id');

                $.ajax({
                    url: this.detailsUrl, //this must be the instance of the class
                    data: { id: problemId },
                    type: 'POST',
                    success: (result) => {
                        $('#details-modal-placeholder').html(result);
                        $('#details-modal-placeholder modal').modal('show');
                    },
                })
            });
        }
    }
}

Javascript

var Problem;
(function (Problem) {
    var Index = (function () {
        function Index() {
            var _this = this;
            $('.problem-detail-button').click(function (e) {
                e.preventDefault();
                var $row = $(_this).closest('tr');
                var problemId = $row.data('problem-id');
                $.ajax({
                    url: _this.detailsUrl,
                    data: {
                        id: problemId
                    },
                    type: 'POST',
                    success: function (result) {
                        $('#details-modal-placeholder').html(result);
                        $('#details-modal-placeholder modal').modal('show');
                    }
                });
            });
        }
        return Index;
    })();
    Problem.Index = Index;    
})(Problem || (Problem = {}));

Now the problem is that the line

var $row = $(this).closest('tr'); //this must be that of the callback

and this line

this.detailsUrl, //this must be the instance of the class

conflict in the meaning of 'this'

How do you handle the mixture of the 'this'?

Upvotes: 16

Views: 12073

Answers (6)

Jude Fisher
Jude Fisher

Reputation: 11294

module Problem {
export class Index {
    detailsUrl: string;
    constructor() {
        var that = this;
        $('.problem-detail-button').click(function (e) {
            e.preventDefault();
            var $row = $(this).closest('tr'); //this must be that of the callback
            var problemId: number = $row.data('problem-id');

            $.ajax({
                url: that.detailsUrl, //this must be the instance of the class
                data: { id: problemId },
                type: 'POST',
                success: (result) => {
                    $('#details-modal-placeholder').html(result);
                    $('#details-modal-placeholder modal').modal('show');
                },
            })
        });
    }
}
}

Explicitly declare that = this so you have a reference for that.detailsUrl, then don't use a fat arrow for the click handler, so you get the correct this scope for the callback.

Upvotes: 16

hulkyuan
hulkyuan

Reputation: 71

module Problem {
export class Index {
    constructor() {
        $('.classname').on('click',$.proxy(this.yourfunction,this));
    }
    private yourfunction(event){
        console.log(this);//now this is not dom element but Index
    }
}
}

check about jquery.proxy(). just remind you there is another way.

Upvotes: 0

pettys
pettys

Reputation: 2468

Late to the thread, but I have something different to suggestion.

Instead of:

var $row = $(this).closest('tr'); //this must be that of the callback

Consider using:

var $row = $(e.currentTarget).closest('tr');

As in this example, anywhere you might want to use this in a jQuery callback, you have access to a function parameter you can use instead. I would suggest that using these parameters instead of this is cleaner (where "cleaner" is defined as more expressive and less likely to be turned into a bug during future maintenance).

Upvotes: 0

user1106925
user1106925

Reputation:

If you're only supporting browsers that have .addEventListener, I'd suggest using that to associate your data with your elements.

Instead of implementing your code, I'll just give a simple example.

function MyClass(el) {
    this.el = el;
    this.foo = "bar";
    el.addEventListener("click", this, false);
}

MyClass.prototype.handleEvent = function(event) {
    this[event.type] && this[event.type](event);
};

MyClass.prototype.click = function(event) {
    // Here you have access to the data object
    console.log(this.foo); // "bar"

    // ...and therefore the element that you stored
    console.log(this.el.nodeName); // "DIV"

    // ...or you could use `event.currentElement` to get the bound element
};

So this technique gives you an organized coupling between elements and data.

Even if you need to support old IE, you can shim it using .attachEvent().

So then to use it, you just pass the element to the constructor when setting up the data.

new MyClass(document.body);

If all the logic is in your handler(s), you don't even need to keep a reference to the object you created, since the handlers automatically get it via this.

Upvotes: 2

basarat
basarat

Reputation: 276189

You need to fallback to the standard way of javascript. i.e store the variable as :

var self = this; 

Then you can use function instead of ()=> and use this to access variable in callback and self to access the instance of the class.

Here is the complete code sample:

module Problem {
    export class Index {
        detailsUrl: string;
        constructor() {
            var self = this; 
            $('.problem-detail-button').click(function(e){

                e.preventDefault();

                var $row = $(this).closest('tr'); //this must be that of the callback
                var problemId: number = $row.data('problem-id');

                $.ajax({
                    url: self.detailsUrl, //this must be the instance of the class
                    data: { id: problemId },
                    type: 'POST',
                    success: (result) => {
                        $('#details-modal-placeholder').html(result);
                        $('#details-modal-placeholder modal').modal('show');
                    },
                })
            });
        }
    }
}

// Creating 
var foo:any = {};
foo.x = 3;
foo.y='123';

var jsonString = JSON.stringify(foo);
alert(jsonString);


// Reading
interface Bar{
    x:number;
    y?:string; 
}

var baz:Bar = JSON.parse(jsonString);
alert(baz.y);

And your generated javascript:

var Problem;
(function (Problem) {
    var Index = (function () {
        function Index() {
            var self = this;
            $('.problem-detail-button').click(function (e) {
                e.preventDefault();
                var $row = $(this).closest('tr');
                var problemId = $row.data('problem-id');
                $.ajax({
                    url: self.detailsUrl,
                    data: {
                        id: problemId
                    },
                    type: 'POST',
                    success: function (result) {
                        $('#details-modal-placeholder').html(result);
                        $('#details-modal-placeholder modal').modal('show');
                    }
                });
            });
        }
        return Index;
    })();
    Problem.Index = Index;    
})(Problem || (Problem = {}));
var foo = {
};
foo.x = 3;
foo.y = '123';
var jsonString = JSON.stringify(foo);
alert(jsonString);
var baz = JSON.parse(jsonString);
alert(baz.y);

Upvotes: 9

Jimmery
Jimmery

Reputation: 10119

I normally bind this to a variable as soon as I have it in the scope I want.

However the this you are after could be found like this:

constructor() {
    var class_this=this;
    $('.problem-detail-button').click(function (e) {
        e.preventDefault();
        var callback_this=e.target;

Upvotes: 1

Related Questions