`Track by $ index` problems with Angular UI Carousel
$ index in track by index does not start at zero when using pagination
I have created a carousel using angular ui bootsrap. Since I am uploading so many images (over 1000), I have used a filter to display 500 paginated images.
.filter('pages', function () {
return function (input, currentPage, pageSize) {
if (angular.isArray(input)) {
var start = (currentPage - 1) * pageSize;
var end = currentPage * pageSize;
return input.slice(start, end);
}
};
})
Controller:
self.currentPage = 1;
self.itemsPerPage = 500;
self.maxSize = 10;
//imagesUrls is response.data from http call
angular.forEach(imagesUrls, function (parent) {
var date = uibDateParser.parse(parent.fileCreationTime, 'M/d/yyyy
hh:mm:ss a');
// fill our slide arrays with urls
// model update here
self.slides.push({
image: parent.fileName,
time: date,
id: parent.id
});
});
self.totalItems = self.slides.length;
And I use it like this:
<div uib-carousel
active="$ctrl.active"
interval="$ctrl.myInterval"
no-wrap="$ctrl.noWrapSlides"
no-pause="true">
<div uib-slide ng-repeat="slide in $ctrl.slides | pages:
$ctrl.currentPage : $ctrl.itemsPerPage track by $index"
index="$index">
<img id="carousel-img" ng-src="{{slide.image}}">
<div class="carousel-caption">
<p>Index {{$index}}</p>
</div>
</div>
</div>
<div class="row">
<ul uib-pagination
total-items="$ctrl.totalItems"
items-per-page="$ctrl.itemsPerPage"
ng-model="$ctrl.currentPage"
max-size="$ctrl.maxSize"
boundary-link-numbers="true"
force-ellipses="true"
class="pagination-sm">
</ul>
</div>
This works as expected.
When the carousel is loaded first, the index is 0. When it moves to the next slide, the index is 1, when you move to the next slide, the index is 2. When you show the slide.id of the current image, it is also 2.
Problem:
However, when you click on the second page link, the index does not go back to zero, it starts from the last index when the slide was in the carousel.
So now the index is 2 and the slide id of the current image is 502.
If you slide to index 20 and you click the pagination link, the index is still at 20. When you show the slide ID of the current image, it becomes 520.
Is there a way to make the index start at 0 so that slide.id is 500 and not 502 or 520?
Hope my question is clear.
source to share
Avoid track by $index
when there is a unique property ID to work with. When working with objects that are unique (as in this case), it is better to allow ng-repeat
native tracking instead of being overridden with track by $index
.
<div uib-slide ng-repeat="slide in $ctrl.slides | pages:
$ctrl.currentPage : $ctrl.itemsPerPage track by ̶$̶i̶n̶d̶e̶x̶ slide.id"
index="$index">
<img id="{{slide.id}}" ng-src="{{slide.image}}">
<div class="carousel-caption">
<p>Index {{ ̶$̶i̶n̶d̶e̶x̶ slide.id}}</p>
</div>
</div>
From Docs:
If you are working with objects with a unique ID property, you should keep track of that ID instead of an object instance. If you reload your data later, you
ngRepeat
don't have to rebuild DOM elements for already rendered elements, even if the JavaScript objects in the collection have been replaced with new ones. For large collections, this significantly improves rendering performance.
DEMO
angular.module("app",['ngAnimate', 'ngSanitize', 'ui.bootstrap'])
.controller("ctrl", function(uibDateParser) {
var self = this;
self.currentPage = 1;
self.itemsPerPage = 10;
self.maxSize = 10;
var url = '//unsplash.it/200/100';
var imagesUrls = [];
for (let i=0; i<40; i++) {
var slide = {
fileName: url+"?image="+(1000+i),
id: 'id'+(0+i+100),
fileCreationTime: new Date()
}
imagesUrls.push(slide);
}
self.slides = [];
//imagesUrls is response.data from http call
angular.forEach(imagesUrls, function (parent) {
var date = uibDateParser.parse(parent.fileCreationTime,
'M/d/yyyy hh:mm:ss a');
// fill our slide arrays with urls
// model update here
self.slides.push({
image: parent.fileName,
time: date,
id: parent.id
});
});
//console.log(self.slides);
self.totalItems = self.slides.length;
})
.filter('pages', function () {
return function (input, currentPage, pageSize) {
if (angular.isArray(input)) {
var start = (currentPage - 1) * pageSize;
var end = currentPage * pageSize;
return input.slice(start, end);
}
};
})
<script src="//unpkg.com/angular/angular.js"></script>
<script src="//unpkg.com/angular-animate/angular-animate.js"></script>
<script src="//unpkg.com/angular-sanitize/angular-sanitize.js"></script>
<script src="//unpkg.com/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js"></script>
<link href="//unpkg.com/bootstrap/dist/css/bootstrap.css" rel="stylesheet">
<body ng-app="app" ng-controller="ctrl as $ctrl">
<div class="container" uib-carousel
active="$ctrl.active"
interval="$ctrl.myInterval"
no-wrap="$ctrl.noWrapSlides"
no-pause="true">
<div uib-slide ng-repeat="slide in $ctrl.slides | pages:
$ctrl.currentPage : $ctrl.itemsPerPage track by slide.id"
index="$index">
<img id="{{slide.id}}" ng-src="{{slide.image}}">
<div class="carousel-caption">
<p>Index {{slide.id}}</p>
</div>
</div>
</div>
<div class="row container">
<ul uib-pagination
total-items="$ctrl.totalItems"
items-per-page="$ctrl.itemsPerPage"
ng-model="$ctrl.currentPage"
max-size="$ctrl.maxSize"
boundary-link-numbers="true"
force-ellipses="true"
class="pagination-sm">
</ul>
</div>
</body>
source to share