Implementing Join in Hadoop Map-Reduce
7 May 2013ASPState database – A performance bottleneck
29 July 2014Tags
Published by
BluePi
Data-Driven Business Transformation
Implement Infinite Scroll Pagination in AngularJS
I bet you have seen and pretty used to the old fashioned pagination that looks something like below:
The pagination style above suffers from a significant UX issues. The user has to guess on what page is the result he is looking for. He clicks and clicks in the hope of finding the right page and finally gets frustrated and leaves your site.
Also, you are asking too many inputs from user to traverse between the pages. Previous, Next, Page Numbers .. turns out though this is something that users have really gotten used to. As a developer too, such a pagination scheme is very easy to implement as it loads limited data .
How will you react if I improve user’s experience by changing multiple pages to a single page, user does not need to click between pages because all the records are being shown in single page now & I guess that makes him happy! At the same time you don’t need to return entire data set at once which means you can still send same chunk of data records as you were sending earlier, but bonus point here is that you need to send data again which you sent earlier, i.e. if you have already sent records 11-20 and user wants to see those again, request will not come to server, client will handle it internally.
Let's do the magic
You can refer first column where browser displays only few records i.e. first page as in older pagination approach. We will observe scroll down event by the user and when we find out that user has reached bottom of record list then we will initiate an AJAX request to get next page record, do not replace current page’s record but append to current record list. Now user will have a experience that he is scrolling down same page, but you are sending data in chunks with the help of AJAX request.
I am going to implement this design in AngularJS, but you can implement this as per your framework as well. I will take an example of Courses where application is showing list of courses.
Back-end Service Contract
You need to define service method which can provide you data accordingly. We need 2 method as follows:
- getCourseCount() and it will get me total no. of courses exists in system so that application can stop loading more once it reaches count limit. url: /v1/courses?count
- getCourses(offset:Number, limit:Number) and it return list of records start from offset and upto limit records. Url: /v1/courses/offset/:offset/limit/:limit
Directive to Load Records on Scroll Down
Now application needs a directive which you can reuse throughout the application for all the pagination. This directive should be generic enough and should take a callback method as parameter so that each page can call its specific logic to load next chunk of records.
angular.module('app.directives.pvScroll', []) .directive('pvScrolled', function() { return function(scope, elm, attr) { var raw = elm[0]; elm.bind('scroll', function() { if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) { scope.$apply(attr.pvScrolled); } }); }; });
Above directive will bind an event ‘scroll’ to current element and as soon it reaches bottom of current element, it will invoke function passed in as parameter while applying this directive on an element.
Template to Render List
Use above coded directive in your view template so that it bind ‘scroll’ event with your container. Container could be anything either your full browser window or just a fraction of page e.g. a div showing list of records, in this case you need to define a height of container so that whenever scroll reaches that height end it should fire an event and load more records. Apply following CSS to your container.
.courses-list{ position : relative; width : 100%; height : 500px; overflow : hidden; }
Assign above defined CSS class to container and bind pvScrolled directive to it as well as follows:
Controller to Load Records on scroll down
In Controller we need to
angular.module('app.search.courses', [ 'ui.state', 'app.services.course.courseService' ]) .config(['$stateProvider', function (stateProvider) { stateProvider .state('courses', { url: '/courses', views: { 'headerView@': { templateUrl: 'templates/header.tpl.html' }, '': { templateUrl: 'courses/templates/courses.tpl.html', controller: 'CourseCtrl' }, 'footerView@': { templateUrl: 'templates/footer.tpl.html' } } }); }]) .controller('CourseCtrl', ['$scope', 'CourseService', function (scope, CourseService) { scope.pagination = { noOfPages: 1, currentPage: 0, pageSize: 10 }; scope.resultList = []; scope.loadMoreCourses = function () { if (scope.loadingResult) { return; } if (scope.pagination.currentPage >= scope.pagination.noOfPages) { return; } scope.pagination.currentPage = scope.pagination.currentPage + 1; scope.offset = (scope.pagination.currentPage - 1) * scope.pagination.pageSize; scope.limit = scope.pagination.pageSize; scope.loadingResult = true; scope.resultList = CourseService.courses.list({offset:scope.offset, limit:scope.limit}); scope.loadingResult = false; }; scope.initializeResultList = function () { CourseService.courses.count({}, function (count) { scope.total = count; scope.pagination.noOfPages = Math.ceil(count / scope.pagination.pageSize); scope.loadMoreCourses(); }); } scope.initializeResultList(); }]);
In the controller, we have declared a scope.pagination which captures pageSize i.e. limit or chuck size we are loading for every request, noOfPages i.e. total no. of requests required to traverse whole list and last is currentPage i.e. last accessed page so that if new request comes in to load more it will calculate which next chunk required to be loaded. There are 2 methods defined too, loadMoreCourses() which will be called every time scroll reached bottom of container or when page loaded first time.This method has check that if method has already initiated load more AJAXrequest, it will not initiate next one. Other is initializeResultList() , which will called one time at the time loading of controller and it will set total no of records and loads first chunk of records. In html code, we have declared 3 different divs other than result list:
- Loading div: It will be shown whenever a AJAX request to load more records is in-progress.
- No More Records to load: It will be shown whenever user has reached end of all the records available in the system. It means application will not load more records.
- No Record found: If there is no record to show at all, then it will be show.
- Please refer the similar implementation like we discussed above