AngularJSでDOMの更新を監視する with iScroll4
iScrollをAngularJSと使いたい案件があったので、それを使うためにいろいろ調べた。
DOMを動かしてScroll
position:fixed
等、モバイルでは一部バグがあったり非サポートだったりして、その大体にiScrollやscrollerjs等といったライブラリを用いる。
これらはDOMをCSSとJSで動かしてあたかもスクロールしているかのように見せるライブラリだ。
いろいろライブラリがあるけれど、とりあえずiScroll4
を使った。
一度使ったことがあったし、ng-iscroll
も使っていたので。
ng-iScroll
Angular x iScrollを使うとなった時、google先生に問うと真っ先にでてくるのだけど、個人的な都合で利用を避けた。
DOMの変更を監視
ng-iScrollのやり方に沿えば、実際iScroll自体は動いたのだけれど、そのままだと、Ajaxで取得したHTML系のコンテンツが非同期的に投入されたときにiscroll.refresh()
してくれない。
そこでどうしたかっていうのが本題。
$watchを使う
以下のコードを見てくれ、どう思う?
scope.$watch(function () { scroller.refresh() });
・・・実はこれでも、XHRで取得したDOMを追加したときにrefreshしてくれる。 でもこの使い方間違っているので注意。
ドキュメントを見てもらえればわかるけれど、$watch
の第一引数は、監視する値を示していて、変更時に走らせる処理は第2引数に指定しなければならない。
一応、第1引数であるwatchExpression
は、functionを渡すことも可能で、値の変更時に呼ばれるのでこれでも動くが、明らかにBKであり、間違っている。
ではどうすればいいのか。
それを知るには、$watch
を含めたscope
のライフサイクルを理解しなければならない。
$watchと$digest
$watchの仕組みや動くタイミングは以下の記事が丁寧にかかれているので参考になった。
$watch
は$digest
で呼ばれており、XHRや値の変更時等にdigest
が呼ばれる。
以下の記事を併せて読むとより理解が深まるんじゃないかと。
AngularJS の $watch, $digest, $apply について書く
DOMの高さの更新を検知したい
さて、$watch
で登録したfunctionがいつどこで呼ばれるかがなんとなくわかったと思う。
今回僕が知りたかったのはiScrollの対象としたDOMの高さが変わったタイミングだ。 つまり、こうすれば取れるはず。
angular.module("myApp").directive("ngScrollable", function () { return { restrict: "A", link: function (scope, elements, attr) { var element = elements[0]; var content = element.children[0]; // スクロールさせたいコンテンツ。適宜セレクタは書き換えるべし。 var scroller = new iScroll(element, {}); scope.$watch(function () { return content.clientHeight }, function { scroller.refresh(); }); } } });
ここまでで2時間かかった・・・ Angular奥が深い