3

I got a very large list of about 200 items with text and images. ng-repeat is way to slow to render this smoothly. It tried it with this solution. Works nice. But not with collection-repeat.

My web-service return this:

enter image description here

There are events with specific dates. The events should be grouped by date. So in order to use collection repeat, how is it possible to insert dividers, if you cant use angular.filter groupBy?

Community
  • 1
  • 1
m1crdy
  • 1,371
  • 2
  • 25
  • 58

1 Answers1

5

I can offer you a partial solution which would only work if the dataset is ordered by the displayed field in the divider.

First of all we need to create a fake element in the array so that we can discriminate the divider amongst the other element.

Let's say we have a collection of posts fetched from a webservice:

.controller('mainController', function($scope, dataService) {
    $scope.posts = [];
    var divider = '';
});

the private field divider will be in use when we load the posts.

And we will have the loadMore method to load extra data when we scroll the list:

$scope.loadMore = function(argument) {
    page++;
    dataService.GetPosts(page, pageSize)
      .then(function(result) {
        if (result.data.length > 0) {
            angular.forEach(result.data, function(value, key) {
              value.divider = false;
              if (value.postId !== divider)
              {
                divider = value.postId;
                $scope.posts.push({divider: true, dividerText: value.postId});
              }
              $scope.posts.push(value);
            });
        }
        else {
            $scope.theEnd = true;
        }
    })
    .finally(function() {
        $scope.$broadcast("scroll.infiniteScrollComplete");
    });
};

When we fetch the data from the web api (and the promise is resolved) we loop through the collection and check if the field is different from the divider. If this is a new divider we store the info and add a new element to the collection:

angular.forEach(result.data, function(value, key) {
    value.divider = false;
    if (value.postId !== divider)
    {
        divider = value.postId;
        $scope.posts.push({divider: true, dividerText: value.postId});
    }
    $scope.posts.push(value);
});

As you can see I've added an element:

$scope.posts.push({divider: true, dividerText: value.postId});

I've used a dividerText field which will be displayed later on.

Now we need to create our own directive divider-collection-repeat which should be attached to a collection repeat:

<ion-item collection-repeat="post in posts" item-height="75" divider-collection-repeat>

I guess you're using infinite-scroll, so here is the whole HTML:

  <ion-content ng-controller="mainController">
    <ion-list>
      <ion-item collection-repeat="post in posts" item-height="75" divider-collection-repeat>
          {{post.name}}
    </ion-item>
  </ion-list>
  <ion-infinite-scroll ng-if="!theEnd" on-infinite="loadMore()" distance="50%"></ion-infinite-scroll>
  </ion-content>

this is the directive:

.directive('dividerCollectionRepeat', function($parse) {

    return {
        priority: 1001,
        compile: compile
    };

    function compile (element, attr) {
      var height = attr.itemHeight || '75';
      var itemExpr = attr.collectionRepeat.split(' ').shift();
      attr.$set('itemHeight', itemExpr + '.divider ? 40 : (' + height + ')');
      attr.$set('ng-class', itemExpr + '.divider ? "item-divider" : ""');
      var children = element.children().attr('ng-hide', itemExpr + '.divider');
      element.prepend(
        '<div ng-show="' + itemExpr + '.divider" class="my-divider" ' +
        'ng-bind="' + itemExpr + '.dividerText" style="height:100%;">' +
        '</div>'
      );

      return function postLink(scope, element, attr) {
         scope.$watch(itemExpr + '.divider', function(divider) {
            element.toggleClass('item-divider', !!divider);
         });
      };  

    }

});

The directive prepends an element (html) to the list using the expression you've defined in your collection-repeat.

In my sample I've use collection-repeat="post in posts" so this line:

var itemExpr = attr.collectionRepeat.split(' ').shift();

fetches the item's name; in my case it is going to be post.

We use the height as well cause we might need to have a different height for the divider.

This bit here is the place where all the magic happens:

  element.prepend(
    '<div ng-show="' + itemExpr + '.divider" class="my-divider" ' +
      'ng-bind="' + itemExpr + '.dividerText" style="height:100%;">' +
    '</div>'
  );

It uses an ng-show for the field 'post.divider' (ng-show="' + itemExpr + '.divider") and binds the our text field ng-bind="' + itemExpr + '.dividerText"

I've also added a custom class my-divider just in case we need to change the layout of our divider a bit.

The final result is here or in this plunker.

As you might have noticed I haven't used a date field as I already had a sample where, sadly, I didn't have any dates. I guess it should be very easy to adapt to your situation.

The directive is based on a sample I have found on github. You will find the code for the directive here.

LeftyX
  • 35,328
  • 21
  • 132
  • 193
  • Hey lefty, thanks for that great example. I´ll try this tomorrow and give you feedback. At the moment I´m not using infinite-scroll. Need to think about how I can implement this with my php webservice. But the events returned are sorted by date with SQL. So I think your solution should work. Thank you! – m1crdy Oct 09 '15 at 14:48
  • @m1crdy: no worries. You don't have to use infinite-scroll. This is another [ionic play](http://play.ionic.io/app/107538017add) without infinite-scroll. Hope it helps. Cheers. – LeftyX Oct 09 '15 at 15:42
  • Works great! Renders perfect. The only thing I can´t solve: If I start scrolling I get "Maximum call stack size exceeded" error. Any guesses? – m1crdy Oct 12 '15 at 10:42
  • It must be smth with the directive. If I´m not including it, there are no call stack errors – m1crdy Oct 12 '15 at 10:43
  • Found it: the ionSticky directive causes the error. https://github.com/Poordeveloper/ion-sticky. Thanks for your help! – m1crdy Oct 12 '15 at 10:50
  • 1
    Ah, great. I was trying to find the cause of the problems :-) Glad I've helped. Cheers. – LeftyX Oct 12 '15 at 10:59