読者です 読者をやめる 読者になる 読者になる

【onsenui】無限スクロールを実装する

はじめに

今回やりたかったのはよくある無限スクロール。
リストの1番下までスクロールしたら次のデータを取得しにゆきます。
これをAngular + Onsen UIで実装したのでメモしておく。

f:id:yoppy0066:20170214111121g:plain:w250

データ数の多いリストはパフォーマンス的にons-lazy-repeatを使うとよいらしい。
そちらについてはこちらでも試してみた。

実装

テンプレート

HTMLはこんな感じで実装。ons-lazy-repeatを使った基本的な形。
リストの最下部に読込中アイコンを置いて、状態によって表示・非表示を切り替えるようにする。

<ons-page ng-controller="MainCtrl">
  <ons-toolbar>
    <div class="center">リスト</div>
  </ons-toolbar>
  <ons-list>
    <ons-list-item ons-lazy-repeat="delegate">
      {{ item.name }}
    </ons-list-item>
  </ons-list>
  <ons-row ng-if="progress">
    <ons-col style="text-align:center;">
      <ons-icon size="30px" spin icon="md-spinner"></ons-icon>
    </ons-col>
  </ons-row>
</ons-page>
メイン処理

必要な処理としては以下
APIに接続してデータを取得してくる
・1番下までスクロールしたらAPIに接続して次のデータを取得しにいく

1番下までスクロールしたかの判定はこんな感じ
.page__contentを対象にするのがポイントぽい

angular.module('sampleListApp')
    .controller('MainCtrl', function () {

        $('.page__content').on('scroll', function(e){
            var elm = $(e.currentTarget);
            if (elm[0].scrollHeight <= elm.height() + elm.scrollTop()) {
              // ココに1番下までスクロールした時の処理を記述
            }
        });

次はAPI接続部分
適当だけどAPIからはこんな感じのJSONが返ってくる想定

{
  contents: [
    {id:1, "アイテム1"},
    {id:2, "アイテム2"},
    {id:3, "アイテム3"},
    ・・・
  ]
}

取得部分をfactoryとして外だし

angular.module('SampleApp')
    .factory("ApiManager", function($http) {
        return {
            callApi: function(offset, callback) {
                $http({
                    method: 'GET',
                    url: "http://path/to/api?offset="+offset
                }).then(function successCallback(response) {
                    callback(respons.contents);
                }, function errorCallback(response) { });

            }
        };
    });

で、完成形はこんな感じに

angular.module('sampleApp')
    .controller('MainCtrl', function ($scope, $http, ApiManager) {

        $scope.items = [];
        $scope.progress = true;

        ApiManager.callApi(0, function(contents) {
            $scope.items = contents;
            $scope.progress = false;
        });
        $('.page__content').on('scroll', function(e){
            var elm = $(e.currentTarget);
            if ($scope.progress == false &&
                elm[0].scrollHeight <= elm.height() + elm.scrollTop()) {
                $scope.progress = true;
                ApiManager.callApi($scope.items.length, function(contents) {
                    for (var i = 0; i < contents.length; i++) {
                        $scope.items.push(contents[i]);
                    }
                    $scope.progress = false;
                });
            }
        });

        $scope.delegate = {
            configureItemScope: function(index, itemScope) {
                itemScope.item = $scope.items[index];
            },
            countItems: function() {
                return $scope.items.length;
            }
        };
    });

また、ずらずらかいたけど以上です。