Infinite scroll with Angular-Data

  • Oct 22

It is quite common to have infinite scroll for mobile app and Ionic framework indeed support it like this:

<ion-content has-header="true" has-subheader="true">
  <ion-list>
    <ion-item collection-repeat="item in items"
              collection-item-width="'100%'"
              collection-item-height="70"
              style='width:100%'>
      <h3>{{item.title}}</h3>
      <p>{{item.content}}</p>
    </ion-item>
  </ion-list>
  <ion-infinite-scroll ng-if="moreDataCanBeLoaded" icon="ion-loading-c" on-infinite="loadMoreData()" distance="20%">
  </ion-infinite-scroll>
</ion-content>

And it is naturally to do this with angular-data in controller like this:

.controller('ItemsCtrl', ['$scope', '$state', 'Item', function ($scope, $state, Item) {
    $scope.items = [];
    $scope.current_page = 1;
    $scope.page_size = 25;
    $scope.moreDataCanBeLoaded = true;

    $scope.loadMoreData = function() {
      params = {page:$scope.current_page};
      Item.findAll(params).then(function(result) {
        if (result.length < $scope.page_size) {
          $scope.moreDataCanBeLoaded = false;
        }
        $scope.items = $scope.items.concat(result);
        $scope.$broadcast('scroll.infiniteScrollComplete');
        $scope.current_page = $scope.current_page+1;
      })
    };
  }])

And the corresponding server side pagination with Rails & Kaminari look like this:

def index
  @items = Item.page(params[:page]).per(params[:page_size])
end

In such case, you can retrieve index data through pagination like this:

http://localhost:3000/items.json?page=1

Ionic render the $scope.items until it reaches the bottom and call $scope.loadMoreData(), which in turns call the server with page information and decide when to stop the infinite scroll.

While it looks all right, it doesn’t. If you use Ionic nagivation to go to other pages and come back, loadMoreData() will not retrieve new data from server because it is cached, thus, loadMoreData() will return empty array and $scope.items will have no data. As a result, your page turn blank. Surely you can turn off the cache, but then you miss the whole point of using angular-data.

To fix it, you shouldn’t depend on loadMoreData() to fill up $scope.items. loadMoreData() is used to fill up the data storage. You should fill up the $scope.items from data storage. Here is the correct way to do so:

.controller('ItemsCtrl', ['$scope', '$state', 'Item', function ($scope, $state, Item) {
  $scope.current_page = 1;
  $scope.page_size = 25;
  $scope.moreDataCanBeLoaded = true;
  $scope.items = Item.filter({});

  $scope.loadMoreData = function() {
    params = {page:$scope.current_page};
    Item.findAll(params).then(function(result) {
      if (result.length < $scope.page_size) {
          $scope.moreDataCanBeLoaded = false;
      }
      $scope.$broadcast('scroll.infiniteScrollComplete');
      $scope.current_page = $scope.current_page+1;
    })
  };
}]) 

$scope.items is hooked up with Item.filter({}) and whenever the data storage is filled, $scope.items is updated. Thus, in loadMoreData(), there is not need to manually add return data into $scope.items. Item.filter({}) will handle it automatically.

The next issue is how to know there is no more data. Since loadMoreData will return empty array if data is in storage already, it is not reliable to check the number of returned data to know whether there is no more data. Here is the solution I have so far and it works:

.controller('ItemsCtrl', ['$scope', '$state', 'Item', function ($scope, $state, Item) {                                                         
  $scope.current_page = 1;
  $scope.page_size = 25;
  $scope.moreDataCanBeLoaded = true;
  $scope.items = Item.filter({});

  $scope.loadMoreData = function() {
    params = {page:$scope.current_page};
    Item.findAll(params).then(function(result) {
      if ((result.length < $scope.page_size) &&
           ($scope.items.length < $scope.current_page * $scope.page_size))
      {
          $scope.moreDataCanBeLoaded = false;
      }
      $scope.$broadcast('scroll.infiniteScrollComplete');
      $scope.current_page = $scope.current_page+1;
    })
  };
}]) 

Then you have a perfectly working infinite scroll with angular-data and ionic framework.