QA@IT

knockoutjs+typescriptでajax通信結果をbindさせる方法

4117 PV

下記のHTMLがあり、valにGetValuesで実行したajax通信の結果をバインドさせています。

<span data-bind="text: val"></span>
<button type="button" data-bind="click: GetValues">
    Button
</button>

javascriptで以下のように書くと正常にvalに通信結果がバインドされます。

var ValueViewModel = function () {
    var self = this;
    self.val = ko.observable("test2");
    self.GetValues = function () {
        $.ajax({
            type: "GET",
            url: "http://localhost:65385//api/MyTask/5/",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (data) {
                self.val(data);
            },
            error: function (error) {
                alert(error.status + "<--and--> " + error.statusText);
            }
        });
    }
}
ko.applyBindings(new ValueViewModel());

しかし、上記のjavascriptを以下のtypescriptで書き換えた場合はバインドされません。
試しにHoge()を実行させてみるとバインドされます。
どのような記述方法であればajax通信の結果をバインドさせることができるのでしょうか?
お知恵を拝借頂ければ幸いです。

class ValueViewModel {

    val: KnockoutObservable<string> = ko.observable("piyo");

    GetValues() {
        $.ajax({
            type: "GET",
            url: "http://localhost:65385//api/MyTask/5/",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (data) {
                this.val(data);
            },
            error: function (error) {
                alert(error.status + "<--and--> " + error.statusText);
            }

        });
    }

    Hoge() {
        this.val("hoge");
    }
}
ko.applyBindings(new ValueViewModel());

2016/7/31
自己解決しました。
ajax呼び出し時に、「context: this,」を追加することで解決しました。
thisの指しているモノを定義してあげなければならないようですが、thisの理解が甘くなぜこうするとうまくいくのかイマイチ分かっていませんが、、
とりあえず表題の問題は解決しましたが解説いただける方いらっしゃいますでしょうか?

    GetValues() {
        $.ajax({
            type: "GET",
            url: "http://localhost:65385//api/MyTask/5/",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            context: this,←ここを追加
            success: function (data) {
                this.val(data);
            },
            error: function (error) {
                alert(error.status + "<--and--> " + error.statusText);
            }

        });
    }

回答

ちょっと一部正確な説明でないかもしれませんが、

そもそもの問題としては、thisは実行している関数の状況に応じて変わってくるということです。イベントにハンドリングする場合にはハンドリングの仕方にもよってきますが、それは今回は別の話ですかね。
今回はsuccessプロパティ内でthisを参照しているので、そこでのthisとはsuccessプロパティにとってのthisオブジェクト(たぶんajaxオブジェクト)になります。
そのために最初のjavascriptではselfにthis(ValueViewModel自身)を退避しておき、GetValuesではselfを使ってValueViewModelのvalにアクセスしているわけです。

ところがTypeScriptのコードにはそれに相当するものがありません(コンパイル後のコードを見るとわかると思います)ので、thisはajaxオブジェクト(それかsuccessコールバック関数自体)を指していると思いますので、そこ(this)のval関数を呼び出してもValueViewModelには影響がありません。

で、jQueryのajaxにはそれを解決するために、Callback内のthisをcontextで指定しておいたものに変更する仕組み(prototype.bind関数を使ってるんだと思います)があります。なので、contextプロパティにthis(ajax関数の引数として展開されるので、GetValues実行時のthis、つまりValueViewModel)を設定しておくと、successコールバック実行時のthisがそれになる(successコールバックが、ValueViewModelのコンテキストで実行される)ので問題が解決するということでしょう。


以下は余談です。試してないので間違ってるかもしれません。
(あと、以下のサンプルは説明に必要なところ以外削ってますのでそのままでは動かない)

関数をプロパティとして定義するとTypescriptがthisを退避しておいてくれたりしますが、
今回の問題はsuccessコールバック内のthisなので効果はないでしょう。

class ValueViewModel {
    GetValuesProp = () => {
        console.log(this);
        $.ajax({
            success: function (data) {
                this.val(data);
            },
        });
    }
以下略
var ValueViewModel = (function () {
    function ValueViewModel() {
        var _this = this;                   // 自動退避
        this.GetValuesProp = function () {
            console.log(_this);             // _thisを使ってくれている
            $.ajax({
                success: function (data) {
                    this.val(data);         // ここには影響しないと思う。
                }
            });
        };
以下略

また、試してないですが元のjavascriptの様に別の変数に退避しておけば動作するんじゃないかと思います。

class ValueViewModel {
    GetValues = function() {
        let self = this;                // self
        $.ajax({
            success: function (data) {
                self.val(data);         // self
            },
        });
    }
以下略
var ValueViewModel = (function () {
    function ValueViewModel() {
        this.GetValues = function () {
            var self = this;
            $.ajax({
                success: function (data) {
                    self.val(data);
                }
            });
        };
    }
以下略
編集 履歴 (0)
  • flied_onionさん、とても丁寧な解説本当にありがとうございました。thisについて大分理解できました。提示して頂いたサンプルなども色々触ってさらに理解を深めてみたいと思います!重ねてお礼申し上げます。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。