AngularJSの$watchで複数の変数の変化を同時にチェックする
AngularJSを使っていると、非常にお世話になる$watch。
変数やオブジェクトが変化した事を読み取る事ができます。
データバインディングによってリアルタイムにページの変化を扱えるAngularJSではほぼ必須の機能と言っても過言ではないと思います。
僕が開発しているシステムでもあちこちで利用しています。
変数やオブジェクトの変化を簡単に検知できるのですが、今回は1つの変数と1つのオブジェクト、どちらかが変化した時にイベントを取得したかったので、その方法を備忘録に載せたいと思います。
やり方
通常では変数名を文字列で検知対象を指定します。
$scope.hoge = 'fuga'; $scope.$watch('hoge', function() { // 変数hogeがなんか変化した時の処理 });
$watchの第一引数は関数で書くことができます。
関数使って同じこと書くとこんな感じ。
$scope.hoge = 'fuga'; $scope.$watch(function() { return $scope.hoge; } , function() { // 変数hogeがなんか変化した時の処理 });
つまりこの第一引数のreturnにオブジェクトを指定してやるとobjectの変化を検知できる。
ただ、第3引数を省略した書き方だとオブジェクトのreferenceが変化しないと(中身だけ変わっても無理)検知できないので、中身だけ変化する場合も検知したい時は、第3引数にtrueを加えてやる必要があります。第3引数にtrueを入れるとオブジェクトの中身を毎回走査する事になったり、比較用に旧オブジェクトを持ったりする必要があるので、速度やメモリ消費的にはもちろん悪くなるので注意。
オブジェクトの参照先をまるごとすげ替える時はtrueを付けなくても検知可能。
$scope.hoge = {'fuga': 'fugafuga', 'hoge': 'hogehoge'}; $scope.$watch(function() { return $scope.hoge; } , function() { // 変数hogeがなんか変化した時の処理 }, true);
ここまできたら後はわかるかもしれませんが、このreturnで検知したい変数とオブジェクトをオブジェクトにして指定しちゃえばいいのです。
$scope.hoge1 = 'hoge'; $scope.hoge2 = {'fuga': 'fugafuga', 'hoge': 'hogehoge'}; $scope.$watch(function() { return { 'hoge1': $scope.hoge1, 'hoge2': $scope.hoge2, }; } , function() { // 変数hogeがなんか変化した時の処理 }, true);
これで変数かオブジェクトが変化した時のどちらでも検知できるというわけですね。
オブジェクトの中身を走査するので、オブジェクトが大きくなる程加速度的に速度が悪化するのが悩みどころですね……
活用どころ
今回僕がこれを利用したシーンは検索のサジェストを表示する部分でした。
htmlの構造的に、この検索ボックスとサジェストの部分でcontrollerを分けたかったんですね。
検索ボックスの変数を取得するのはng-modelを使ってcontrollerから引っ張っていけばいいんですが、その入力値からデータを引っ張ってきてサジェストの内容を更新します。
自分の場合はテキストボックスの値の変化を$watchで検知して、変化のたびに$http(ajax)でデータベースから内容をひっぱてきてます。(リクエスト飛ばし過ぎなんでこの辺は色々考えた方がいいとは思います)
非同期通信なんで、入力速度が速いと検索ワードの変化とデータが返ってくるタイミングがずれて、検索ワードと結果が違うものになっちゃったりする訳です。
なので、検索ワードの変化と取得したデータを保持しておく変数の両方を検知して、現在の検索ワードに対応したサジェスト内容を表示するという使い方をしていました。
最後に
他にもいい使い方、お前の無駄なリクエスト送り続けるプログラムこうした方がいいだろ、みたいな話があったらぜひ教えてください。