のらねこの気まま暮らし

技術系だけど、Qiita向きではないポエムとかを書くめったに更新されないヤツ

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の仕組みや動くタイミングは以下の記事が丁寧にかかれているので参考になった。

AngularJS のデータバインドを支える $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奥が深い