Paging with Range Headers using AngularJS and Web API 2 (Part 1)

Recently, I needed to take a page with a list and add paging and sorting to it. We didn’t do it when we first wrote the page because the data set was small and we had higher priorities. However, the time was getting near when people would have to deal with a table of over 100 rows. It sounded simple, until the little trick of letting the client know when not to page. Somehow, I needed to tell the client how many total records there were so it could appropriately disable the Next arrow. I read about and debated lots of methods, but finally settled on using HTTP Range headers. I could write about that, but I can save time by saying that I pretty much ended up agreeing with this guy.

Before I get into how to use Range headers, let me briefly cover the what. The HTTP spec allows for partial downloading of content by having the client ask for the part it needs. One of the standard response headers is Accept-Ranges, and it tells the client whether asking for a range is allowed, and what unit to ask for. In the past, the range unit was typically bytes, which the client could use to download files in pieces. Once it knew that, it would ask for a range of bytes using the Range header. The bytes would be in the response content, and the Content-Range response header would tell the client the unit (again), the start and end of the range, and possibly the length of the file.

Fast forward to our RESTful way of doing things and the range unit becomes something like “customers” and the length is more like the total number of customers available. The client might ask for “customers=0-19” with the expectation of getting the first 20 customers. The server would respond with something like “customers 0-19/137”, meaning that it gave the first 20, and there are 137 total customers.

On the AngularJS side, we use the transformRequest field of the resource to add a transform function that will add the Range header. I tried to use the header field, but that just sets the default header for each getCustomers request and we need the header to change for each call. It looks like this:

var customerServices = angular.module('customerServices',
                                      ['ngResource']);

customerServices.factory('customerService', [
    '$resource',
    function($resource) {
        return $resource('/api/CustomerService', {}, {
            getCustomers: {
                method: 'GET',
                isArray: true,
                transformRequest: function(data, headersGetter) {
                    var headers = headersGetter();
                    headers['Range'] = 'customers='
                        + fromCustomer + '-' + toCustomer;
                }
            }
       });
    }
]);

I’ll skip over the Web API part of it, for now (wait for Part 2), and go over handling the response. In the success block of getCustomers, we get the Content-Range header and parse out the important pieces.

 
customerService.getCustomers({},
     function(value, headers) {
         var rangeFields = headers('Content-Range').split(/\s|-|\//);
         $scope.fromCustomer = parseInt(rangeFields[1]);
         $scope.toCustomer = parseInt(rangeFields[2]);
         $scope.totalCustomers = parseInt(rangeFields[3]);
         // and then do cool stuff...

Now that the client knows the from customer, the to customer, and the total number of customers, it can do all the neat paging stuff it wants. It can enable and disable the Previous or Next control. It could place page number links that let the user skip to whatever page they want. It could have some kind of button that goes all the way to the end or all the way to the beginning, and so on.

Don’t miss Part 2, where I’ll go over the Web API side of this puzzle.

Sample code for this series can be found at: https://github.com/qanwi1970/customer-paging

 

Special thanks to Mathieu Brun for the data ganerator used in the sample (https://github.com/mathieubrun/Cogimator.SampleDataGenerator)

One thought on “Paging with Range Headers using AngularJS and Web API 2 (Part 1)

Leave a comment