Performance issues with ion-lists and simultaneous promises


#1

I have a view for listing users, it calls an endpoint and it receives several arrays for each section of the list. The view can be refreshed using ion-refresher, and the user can follow (or unfollow) any user of that list, calling the method used in on-refresh.

The problem I’m having is that if the user refresh the view multiple times too fast, or if follows/unfollows users quickly, the list or some elements of it go blank, like if the list items don’t have data (but they do). Sometimes when performing multiple follow operations fast an item of the list is not an user but the endpoint response of the follow operation.

I don’t know if the promises used for each operation interfere with each other (as they are not in sequence but each one of them are a single action), or if the view doesn’t have time to render all the items in the case of refreshing multiple times. I tried adding $ionicLoading and track by to the ng-repeat, but the issue persists. Note that at the moment there are few elements rendered, so the quantity of items is not a relevant factor in my opinion.

How can I solve this? Thanks.

Here is the code for the view:

Template

<ion-view title="Contacts">
	<ion-nav-buttons side="primary">
		<!--<button class="button button-icon button-positive icon ion-search" ng-click="vm.showFilterBar()"></button>-->
	</ion-nav-buttons>
  <ion-content overflow-scroll="true" class="has-header contacts" hide-nav-bar="true">
    <ion-refresher
      pulling-text="Pull to refresh..."
      on-refresh="refreshList()">
    </ion-refresher>
    <div class="bar bar-header item-input-inset search">
      <label class="item-input-wrapper">
        <i class="icon ion-ios-search"></i>
        <input ng-model="search_data.query_term" ng-change="searchUsers()" type="search" placeholder="Search" class="text-center">
      </label>
    </div>
    <!-- Searchedusers list  -->
    <div>
      <ion-item ng-repeat="user in searchedusers" class="item item-avatar">
      <img ui-sref="tabsController.users_profile({ username: user.user.username })" ng-src="{{ user.user.profile_picture }}">
      <h2 ui-sref="tabsController.users_profile({ username: user.user.username })">{{ user.user.first_name }}&nbsp;{{ user.user.last_name }}</h2>
      <p ui-sref="tabsController.users_profile({ username: user.user.username })">{{ user.user.username }}</p>
      <div ng-if="user.user.username != username" class="col-center">
        <button ng-if="!user.following_already" class="pull-right button button-outline button-positive icon-left button-follow" ng-click="followUser(user.user.pk)"><i class="ion-plus-round"></i>
        follow</button>
        <button ng-if="user.following_already" class="pull-right button icon-left button-follow button-positive" ng-click="unfollowUser(user.user.pk)">following</button>
      </div>
      </ion-item>
    </div>
    <!-- End list searchedusers -->

    <!-- list Recomended -->
    <ion-list>
      <div class="item item-divider bg-gradient text-center">
        <p>Suggested</p>
      </div>
      <ion-item ng-repeat="user in recomendedUsers" class="item item-avatar">
        <img ui-sref="tabsController.users_profile({ username: user.username })" ng-src="{{ user.profile_picture }}">
        <h2 ui-sref="tabsController.users_profile({ username: user.username })">{{ user.first_name }}&nbsp;{{ user.last_name }}</h2>
        <p ui-sref="tabsController.users_profile({ username: user.username })">{{ user.username }}</p>
        <!-- Buttons, folow commented and Following is showed -->
        <button class="pull-right button button-outline button-positive icon-left button-follow" ng-click="followUser(user.pk)">
        <i class="ion-plus-round"></i>
        follow
        </button>
        <button ng-if="user.following_already" class="pull-right button icon-left button-positive button-follow" ng-click="unfollowUser(user.pk)">following</button>
      </ion-item>
    </ion-list>
    <!-- End Recomended list -->

    <!-- Followers list -->
    <ion-list>
      <div class="item item-divider bg-gradient text-center">
        <p>My Followers</p>
      </div>
      <ion-item ng-repeat="user in followers" class="item item-avatar">
        <img ui-sref="tabsController.users_profile({ username: user.target.username })" ng-src="{{ user.target.profile_picture }}">
        <h2 ui-sref="tabsController.users_profile({ username: user.target.username })">{{ user.target.first_name }}&nbsp;{{ user.target.last_name }}</h2>
        <p ui-sref="tabsController.users_profile({ username: user.target.username })">{{ user.target.username }}</p>
        <!-- Buttons, folow commented and Following is showed -->
        <!-- <button class="pull-right button button-light icon-left button-follow" ng-click="followUser(user.pk)">
        <i class="ion-plus-round"></i>
        Follow
        </button> -->
        <div ng-if="user.target.username != username" class="col-center">
          <button ng-if="!user.following_already" class="pull-right button button-outline  icon-left button-positive button-follow" ng-click="followUser(user.target.pk)">
          <i class="ion-plus-round"></i>
          follow</button>
          <button ng-if="user.following_already" class="pull-right button icon-left button-positive button-follow" ng-click="unfollowUser(user.target.pk)">following</button>
        </div>
      </ion-item>
    </ion-list>
    <!-- End followers list -->

    <!-- list Following -->
    <ion-list>
      <div class="item item-divider bg-gradient text-center">
        <p>Following</p>
      </div>
			<ion-item ng-repeat="user in following" class="item item-avatar">
				<img ui-sref="tabsController.users_profile({ username: user.follows.username })" ng-src="{{ user.follows.profile_picture }}">
        <h2 ui-sref="tabsController.users_profile({ username: user.follows.username })">{{ user.follows.first_name }}&nbsp;{{ user.follows.last_name }}</h2>
        <p ui-sref="tabsController.users_profile({ username: user.follows.username })">{{ user.follows.username }}</p>
        <!-- Buttons, folow commented and Following is showed -->
        <!-- <button class="pull-right button button-light icon-left button-follow" ng-click="followUser(user.pk)">
        <i class="ion-plus-round"></i>
        Follow
        </button> -->
        <button ng-if="user.following_already" class="pull-right button icon-left button-positive button-follow" ng-click="unfollowUser(user.follows.pk)">following</button>
			</ion-item>
		</ion-list>
    <!-- End following list -->
	</ion-content>
</ion-view>

Controller

angular.module('app.controllers')

.controller('userListCtrl', function($scope, store, $controller, $state, $ionicLoading, $ionicPlatform, $ionicFilterBar, followService, searchService, profileInformationService, $cordovaContacts){
  $scope.login_required = true;
  $scope.username = store.get('username');
  $scope.search_data = {query_term:''};
  $controller('mainCtrl', { $scope: $scope });
  $scope.searchUsers = function(){
    $scope.searchedusers = null;
    var name = $scope.search_data.query_term;
    if (name) {
      searchService.getSearchedUsers(name).then(
        function(data){
          $scope.searchedusers = data.data;
        });
    }else{
      $scope.searchedusers = null;
    };
  };
    function onSuccess(contacts) {
    var phone_numbers = [];
    for(var i=0; i < contacts.length; i++){
      if (contacts[i].phoneNumber) {      
        if (contacts[i].phoneNumbers.length>0 || contacts[i].phoneNumbers.length == undefined) {
          for(var j=0; j<contacts[i].phoneNumbers.length; j++){
            phone_numbers.push(contacts[i].phoneNumbers[j].value
            .replace(/[^\+\d]/g,''));
          }
        }
      }
    }
    profileInformationService.getProfileList(phone_numbers).then(
      function(data){
        $scope.recomendedUsers = data.data;
      });
  };

  function onError(contactError) {
      alert('Could not fetch contacts');
  };
  
  $scope.refreshList = function(){
    $scope.searchedusers = null;
    followService.getFollowingList().then(
      function(data){
        $scope.filterBarInstance;
        $scope.following = data.data;
        followService.getFollowers($scope.username).then(
          function(data){
            $scope.followers = data.data;
            profileInformationService.getProfileList().then(
              function(data){
                $scope.recomendedUsers = data.data;
              }).finally(function(){
                $ionicLoading.hide();
              });
              $ionicPlatform.ready(function() {
                var options = new ContactFindOptions();
                options.filter = "";
                options.multiple = true;
                var fields = ["phoneNumbers"];
                navigator.contacts.find(fields, onSuccess, onError, options);
              });
            }, function(error){
              console.log(error);
            });
      },function(error){
        console.log(error)
      }
      ).finally(function(){
        console.log("finished function")
        $scope.$broadcast('scroll.refreshComplete');
      });
    };
    $scope.$on('$ionicView.enter', function(){
      console.log("asdasdas");
      $scope.$apply();
      $scope.refreshList();
      console.log("entered?");
    });

  $scope.followUser = function(user_pk){
    followService.followUser(user_pk, 'create').then(
      function(data){
      }).finally(function(){
        $scope.refreshList();
      });
    };

  $scope.unfollowUser = function(user_pk){
    followService.followUser(user_pk, 'delete').then(
      function(data){
      }).finally(function(){
        $scope.refreshList();
      });
    };
})

Service

angular.module('app.services')

.service('profileInformationService', function($q, $http, store){
  return {
    getProfileList: function(phone_numbers){
      deferred = $q.defer();
      promise = deferred.promise;
      token = store.get('jwt');
      data = {phones: phone_numbers};
      $http({
        url: get_api_url('recommended-users/'),
        method: 'POST',
        data: data,
        headers: {
          'Authorization': 'JWT ' + token
        }
      }).then(function(response){
        deferred.resolve(response);
      }, function(error){
        deferred.reject(error);
      });
      return promise;
    }
  }
});