Simon Price
Simon Price

Reputation: 3261

TypeScript: Uncaught TypeError: this.fnName is not a function

I am working with Typescript and have an ajax call that on Success is calling another function \ method.

 deleteTheseSuccess(data) {
     new Fe.Upsm.Head().showGlobalNotification("Selected Items Deleted");
     this.refreshPrintQueueGrid(); <== this is there the error existst
     (window as any).parent.refreshOperatorPrintQueueCount();
 }

the method call that I have pointed out above is trying to call

refreshPrintQueueGrid() {
    $("#PrintQueueGrid").data("kendoGrid").dataSource.read();
    this.refreshPrintQueueStats();
}

This compiles without any issues in VS2017 and produces the JavaScript files accordingly

and this is the call in the JavaScript output

PrintQueue.prototype.refreshPrintQueueGrid = function () {
    $("#PrintQueueGrid").data("kendoGrid").dataSource.read();
    this.refreshPrintQueueStats();
};

The exact error that I get is

Uncaught TypeError: this.refreshPrintQueueGrid is not a function.

I would be grateful if anyone can help me understand what is going wrong here and what would cause this as I will have this in a number of places over my application.

--- Edit compiled code

PrintQueue.prototype.refreshPrintQueueGrid = function () {
            $("#PrintQueueGrid").data("kendoGrid").dataSource.read();
            this.refreshPrintQueueStats();
        }

--- Edit 2 -- Class

namespace Fe.Upsm {
export class PrintQueue {


    callingView: string

    constructor(callingView: string) {

        this.callingView = callingView;

        this.documentReadyObjects();

    }

    refreshPrintQueueGrid() {
        $("#PrintQueueGrid").data("kendoGrid").dataSource.read();
        this.refreshPrintQueueStats();
    }

    deleteThese(ids) {

        var jsonObj = {
            ids: ids
        }

        var dataAccess = new DataAccess.AjaxDataAccessLayer(Fe.Upsm.Enums.AjaxCallType.Post,
            Fe.Upsm.Enums.AjaxDataType.json,
            "../../PrintQueue/DeletePrintQueueItems",
            jsonObj);

        dataAccess.ajaxCall(this.deleteTheseError, this.deleteTheseSuccess);

    }

    deleteTheseSuccess(data) {
        new Fe.Upsm.Head().showGlobalNotification("Selected Items Deleted");
        this.refreshPrintQueueGrid;
        (window as any).parent.refreshOperatorPrintQueueCount();
    }

    deleteTheseError(xhr) {
        alert("An Error Occurred. Failed to delete print queue items");
    }



}

}

Upvotes: 2

Views: 4853

Answers (2)

Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92537

The problem is that in your ajax callback

dataAccess.ajaxCall(this.deleteTheseError, this.deleteTheseSuccess);

method this.deleteTheseSuccess loose 'this' context (because ajax call it).

You can wrap this function by other that contains this as self variable and pass it to dataAccess.ajaxCall

EDIT

Try something like this (i write code from head so test it):

Add it to class PrintQueue following method

public deleteTheseSuccessWrapper() {
    let self = this;
    return () => { self.deleteTheseSuccess(); };
}

And use it in deleteThese method by:

dataAccess.ajaxCall(this.deleteTheseError, this.deleteTheseSuccessWrapper());

EDIT - version 2

as @estus point out in comments: Why is deleteTheseSuccessWrapper needed? we can try to omit it just by using fat-arrow:

dataAccess.ajaxCall(this.deleteTheseError, () => { this.deleteTheseSuccess() });

So we have oneliner solution - I write above code from head but I think it should also work

Upvotes: 2

Estus Flask
Estus Flask

Reputation: 222760

deleteTheseError and deleteTheseSuccess methods are passed as callbacks. As explained in related umbrella question, there are ways to bind them to proper this context.

It's possible to define these methods as arrow functions, but as this answer explains, a more universal way is to define them as prototype methods and bind them in class constructor:

constructor(callingView: string) {
    this.deleteTheseError = this.deleteTheseError.bind(this);
    this.deleteTheseSuccess = this.deleteTheseSuccess.bind(this);
    ...
}

Besides arrow methods, TypeScript also offers an option to use method decorators, e.g. from bind-decorator package. This provides a nicer syntax for .bind(this):

import bind from 'bind-decorator';

...
@bind
deleteTheseSuccess(data) {...}

@bind
deleteTheseError(xhr) {...}
...

Upvotes: 2

Related Questions