[Knockout.js基本編]ko.observableメソッドでプロパティの変化をリアルタイムでDOMに反映させる

2014年04月16日

カテゴリー:

前回の記事([Knockout.js基本編]最低限の構成で「Hello World」してみる)では、JavaScript側でDOMを操作するためのコードを書くことなくViewModelオブジェクトの内容をDOMに反映させる方法を解説しました。

しかし、このままだとViewModelオブジェクトの内容をDOMに反映させた後に、再びViewModelオブジェクトの内容(プロパティの値)が変化しても、その変更内容はDOM側に通知されず反映もしません。

今回はこのようなケースを解決するための、Knockout.jsの重要な機能「Observable:オブザーバブル=監視可能」について解説します。

コード1:ViewModelオブジェクトの内容を画面に表示する

まず最初のコードは、ViewModelオブジェクトに設定されたプロパティ(num)をView(HTML)に出力するだけのコードです。
画面上に「0」と表示されるだけのコードです。

<html>
  <body>
    <span data-bind="text: num"></span>
    <script src="knockout.js"></script>
    <script src="view-model.js"></script>
  </body>
</html>
window.onload = function () {
    'use strict';

    var i = 0,
        viewModel = {
            num: i
        };

    ko.applyBindings(viewModel);
};

コード2:DOMの描画後にViewModelオブジェクトの内容を変更してみる

次にコード1のview-model.jsの内容を少し修正して、1秒ごとにviewModel.numの値をインクリメントさせてみます。これは、1秒ごとに画面の表示を「1」「2」「3」…と変化させることを期待したコードです。

window.onload = function () {
    'use strict';

    var i = 0,
        viewModel = {
            num: i
        };

    //1秒毎にviewModel.numをインクリメントさせる
    setInterval(function () {
        i += 1;
        viewModel.num = i;
    }, 1000);

    ko.applyBindings(viewModel);
};

確かにviewModel.numの値は1秒毎に1ずつ増加していきますが画面上の表示は何秒経っても「0」のままです。このコードでは、viewModel.numの値を更新させてもその更新内容がView(HTML)側に通知されないからです。

では、viewModel.numの値の変化を逐一View(HTML)側に反映させるにはどうすれば良いのでしょうか?

コード3:ViewModelオブジェクトの内容を追跡しDOMに反映するようにする

最後のコード例は、コード2の内容を修正し、viewModel.numの値が変更された際に、その変更内容をViewに通知できるようにしたものです。

window.onload = function () {
    'use strict';

    var i = 0,
        viewModel = {
            num: ko.observable(i)
        };

    //1秒毎にviewModel.numをインクリメントさせる
    setInterval(function () {
        i += 1;
        viewModel.num(i);
    }, 1000);

    ko.applyBindings(viewModel);
};

コード2との違いは、6行目と12行目です。

Knockout.jsでは、ViewModelオブジェクトのプロパティの内、その値の変化を逐一View(HTML)側に通知・反映させたい場合はobservable(オブザーバブル=監視可能)メソッドを使用します。

コードの6行目では変数 i をそのまま渡すのではなく、ko.observableメソッドの結果を渡し、メソッドの引数として変数 i を渡しています。これでviewModel.numは監視可能な状態となり、引数として渡した値がその初期値となります。

12行目では監視可能な状態であるviewModel.numの値を更新しています。
ko.observableメソッドの戻り値の型は関数なので、この時点でviewModel.numには関数がセットされています。そのため、値を更新する際は、その関数の引数として値を渡す必要があります。

12行目ではついついviewModel.num = i;とやってしまいがちですが、これはNGです。この時点ではviewModel.numは関数なので、viewModel.num = i;としてしまうと関数がただの数値として上書きされてしまうため、6行目でセットしたobservableが機能しなくなるからです。

observableメソッドを使うことにより、viewModel.num の値が監視可能な状態となり、値が更新された際にその更新内容が逐一DOM側に通知されるようになります。

これで1秒ごとに画面の表示が「1」「2」「3」…と変化していくことが確認できるようになるはずです。

どうやらobservable(監視可能)なのはプリミティブだけのよう

ここまででko.observableの基本的な機能と使い方の紹介をしてきましたが、ここで紹介したサンプルコードでは監視対象となるプロパティ(つまり、ko.observableメソッドの引数)は、単純な数値のみでした。もちろん、数値以外にも文字列や、真偽値もko.observableメソッドの引数として渡すことが可能です。

では、このko.observableメソッドの引数にオブジェクトを渡すことは出来るのでしょうか?
例えば、複数のプロパティからなるオブジェクトをko.observableメソッドで監視対象にした場合、そのオブジェクトのプロパティの変化がちゃんとDOMに反映してくれれば、いちいちプロパティごとにko.observableメソッドを使わなくても良いことになりますね。

このことについて、色々と情報を探してみたのですがそれらしい回答を見つけることができなかったので、自分で検証してみることにしました。


まず最初は、ko.observableメソッドを使わずに、単純にオブジェクトの内容を画面に表示させるコードです。

<html>
  <body>
    <span data-bind="text: obj.name"></span>
    <script src="knockout.js"></script>
    <script src="view-model.js"></script>
  </body>
</html>
window.onload = function () {
    'use strict';

    var o = {
            name: 'hoge'
        },
        viewModel = {
            obj: o
        };

    ko.applyBindings(viewModel);
};

上記のコードを実行すると、画面に「hoge」と表示されます。ここまでは問題ありませんね。
では次に、上記のViewModelコードの8行目を以下の様に変更してみます。

window.onload = function () {
    'use strict';

    var o = {
            name: 'hoge'
        },
        viewModel = {
            obj: ko.observable(o)
        };

    ko.applyBindings(viewModel);
};

上記のコードも、画面に「hoge」と表示されることを期待したコードですが、結果は「何も表示されない」でした。
エラーにはならないのですが、どうやらオブジェクトを監視対象にしても、そのオブジェクトのプロパティを読みだすことは出来ないようです。

上記のコードを正しく動作するようにしたものが次のコードです。

window.onload = function () {
    'use strict';

    var o = {
            name: ko.observable('hoge')
        },
        viewModel = {
            obj: o
        };

    ko.applyBindings(viewModel);
};

今度はちゃんと「hoge」が表示されました。

あくまでも私ができる範囲での検証でしたが、どうやらko.observableメソッドに渡すことができるのはプリミティブ(数値、文字列、真偽値)のみの様ですね。

ko.observableメソッドに渡すことができるのはプリミティブ(数値、文字列、真偽値)のみ

今回紹介したko.observableメソッドは、knockout.jsを使う上では欠かせない基本的なメソッドですので、しっかりと身に着けておく必要がありそうです。