Reputation: 1401
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:
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?
Upvotes: 3
Views: 1684
Reputation: 35587
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.
Upvotes: 5