Search code examples
ionic-frameworkcompatibilityinfinite-scrollrefresher

Ionic There is a Bug? ion-refresher and ion-infinite-scroll


I found an compatibility issue using the two mentioned components on same list, my html and js code below: HTML:

    <ion-content ng-controller="homeCtrl">
    <ion-refresher on-refresh="loadNewContent()" pulling-text="LoadMore..." spinner="android"></ion-refresher>
    <div ng-repeat="item in items">

        <a href="#/detail" class="thumb"><img ng-src="{{pathUrl+item['path']}}" style="height: auto;width:100%;"></a>
    </div>
    <ion-infinite-scroll ng-if="hasMore" on-infinite="loadMoreContent()" spinner="spiral" distance="5" immediate-check="false"></ion-infinite-scroll>
</ion-content>

JavaScript:

JiCtrls.controller('homeCtrl', ['$scope', '$timeout', 'DbService', 'JsonService',
function ($scope, $timeout, DbService, JsonService) {
    $scope.items = [];
    $scope.hasMore = true;
    var run = false;
    loadData(0);
    //下拉更新
    $scope.loadNewContent = function () {
        loadData(2);
        // Stop the ion-refresher from spinning
        $scope.$broadcast("scroll.refreshComplete");
    };

    //上拉更新
    $scope.loadMoreContent = function () {
        loadData(1);
        $scope.$broadcast('scroll.infiniteScrollComplete');
    };




    function loadData(stateType) {
        if (!run) {
            run = true;
            if ($scope.sql == undefined) {
                $scope.sql = "select top 5 * from information ";
            }
            DbService.getData($scope.sql, '').success(function (data, status, headers, config) {
                var convertData = JsonService.convertData(data);
                if (stateType == 1 || stateType == 0) {
                    // $scope.items = $scope.items.concat(convertData);
                    for (var i = 0; i < convertData.length; i++) {
                        $scope.items.push(convertData[i]);
                    }

                }
                else {
                    for (var i = 0; i < convertData.length; i++) {
                        $scope.items.unshift(convertData[i]);
                    }

                }

                if (convertData == null || convertData.length <= 0) {
                    $scope.hasmore = false;
       ;
                }
                $timeout(function () {
                    run = false;
                }, 500);

            }).error(function (errorData, errorStatus, errorHeaders, errorConfig) {
                console.log(errorData);
            });
        }
    }
}

]);

Everything is normal in Chrome browser and Iphone, but in a part of the Android phone there is a big problem.When ion-refresher trigger on-refresh function,the on-infinite="loadMoreContent()" function will run indefinitely. So,What is the problem?


Solution

  • Try to put $scope.$broadcast("scroll.refreshComplete"); and $scope.$broadcast('scroll.infiniteScrollComplete'); into DbService.getData(...).success() callback, not in functions triggered by on-infinite and on-refresh.

    Explanation:

    When the user scrolls to the end of the screen, $scope.loadMoreContent which is registered with on-infinite is triggered. The spinner shows, and ion-infinite-scroll pauses checking whether the user has reached the end of the screen, until $scope.$broadcast('scroll.infiniteScrollComplete'); is broadcast, when it hides the spinner and resumes the checking.

    In your code, imagine there's a 3s delay in network, no new item is added to the list in that 3s. As a result, the inner height of ion-content is never updated, so the check that determines whether the user has reached the end of the screen will always return true. And you effectively prevent ion-infinite-scroll from pausing this checking by broadcasting scroll.infiniteScrollComplete the moment on-infinite is triggered. That's why it will update indefinitely.

    To improve your code quality and prevent future problems, you may need to call $scope.$apply in your DbService.getData().success() callback (depending on the implementation of getData) and manually notify ion-content to resize in the callback.

    P.S. 来自中国的Ionic开发者你好 :-) 两个中国人讲英语真累啊


    Update

    I've made a codepen that combines both ion-refresher and ion-infinite-scroll, I think it's working pretty fine.

    http://codepen.io/KevinWang15/pen/xVQLPP

    HTML

    <html ng-app="ionicApp">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    
        <title>ion-refresher + ion-infinite-scroll 2</title>
    
        <link href="//code.ionicframework.com/nightly/css/ionic.css" rel="stylesheet">
        <script src="//code.ionicframework.com/nightly/js/ionic.bundle.js"></script>
    
      </head>
      <body ng-controller="MyCtrl">
    
        <ion-header-bar class="bar-positive">
          <h1 class="title">ion-refresher + ion-infinite-scroll 2</h1>
        </ion-header-bar>
    
        <ion-content delegate-handle="mainScroll">
          <ion-refresher on-refresh="doRefresh()">
    
          </ion-refresher>
          <ion-list>
            <ion-item ng-repeat="item in list">{{item}}</ion-item>
          </ion-list>
    
          <ion-infinite-scroll
            ng-if="hasMore"
            on-infinite="loadMore()"
            distance="1%">
          </ion-infinite-scroll>
        </ion-content>
    
      </body>
    </html>
    

    JS

    angular.module('ionicApp', ['ionic'])
    
    .controller('MyCtrl', function($scope, $timeout, $q, $ionicScrollDelegate) {
      /*
        list of items, used by ng-repeat
      */
      $scope.list = [];
    
      var itemOffset = 0,
        itemsPerPage = 5;
    
      /*
        used by ng-if on ion-infinite-scroll
      */
      $scope.hasMore = true;
    
      /*
        isRefreshing flag.
        When set to true, on data arrive
        it first empties the list 
        then appends new data to the list.
      */
      var isRefreshing = false;
    
      /* 
        introduce a custom dataFetcher instance
        so that the old fetch process can be aborted
        when the user refreshes the page.
      */
      var dataFetcher=null;
    
      /*
        returns a "dataFetcher" object
        with a promise and an abort() method
    
        when abort() is called, the promise will be rejected.
      */
      function fetchData(itemOffset, itemsPerPage) {
        var list = [];
        //isAborted flag
        var isAborted = false;
        var deferred = $q.defer();
        //simulate async response
        $timeout(function() {
          if (!isAborted) {
            //if not aborted
    
            //assume there are 22 items in all
            for (var i = itemOffset; i < itemOffset + itemsPerPage && i < 22; i++) {
              list.push("Item " + (i + 1) + "/22");
            }
    
            deferred.resolve(list);
          } else {
            //when aborted, reject, and don't append the out-dated new data to the list
            deferred.reject();
          }
        }, 1000);
    
        return {
          promise: deferred.promise,
          abort: function() {    
            //set isAborted flag to true so that the promise will be rejected, and no out-dated data will be appended to the list
            isAborted = true;
          }
        };
      }
    
      $scope.doRefresh = function() {
        //resets the flags and counters.
        $scope.hasMore = true;
        itemOffset = 0;
        isRefreshing = true;
        //aborts previous data fetcher
        if(!!dataFetcher) dataFetcher.abort();
        //triggers loadMore()
        $scope.loadMore();
      }
    
      $scope.loadMore = function() {
    
        //aborts previous data fetcher
        if(!!dataFetcher) dataFetcher.abort();
    
        //fetch new data
        dataFetcher=fetchData(itemOffset, itemsPerPage);
    
        dataFetcher.promise.then(function(list) {
          if (isRefreshing) {    
            //clear isRefreshing flag
            isRefreshing = false;
            //empty the list (delete old data) before appending new data to the end of the list.
            $scope.list.splice(0);
            //hide the spin
            $scope.$broadcast('scroll.refreshComplete');
          }
    
          //Check whether it has reached the end
          if (list.length < itemsPerPage) $scope.hasMore = false;
    
          //append new data to the list
          $scope.list = $scope.list.concat(list);
    
          //hides the spin
          $scope.$broadcast('scroll.infiniteScrollComplete');
    
          //notify ion-content to resize after inner height has changed.
          //so that it will trigger infinite scroll again if needed.
          $timeout(function(){
            $ionicScrollDelegate.$getByHandle('mainScroll').resize();
          });
        });
    
        //update itemOffset
        itemOffset += itemsPerPage;
      };
    
    });