Most of us have seen pagination used in one form or another for presenting a collection of data to a user that gives them the ability to page through the data. This is most useful when you’re dealing with large datasets that you don’t want to display all at once on a single page. With Spring Data, a lot of the heavy lifting is done for you and presented in a nice simple interface. Spring Data comes with the PagingAndSortingRepository interface with extra methods for pagination and also sorting. If like me you’re using Spring Data JPA, the JpaRepository already extends the
PagingAndSortingRepository, so you already have the added benefits.
Getting to know Spring Data’s pagination support
Here is a simple Widget repository with a custom finder that returns a paged result.
As you can see, it’s pretty simple. Our finder accepts a Pageable argument and returns a Page of
Widget entities. To make this useful from a web application, Spring Data comes with some nice out of the box functionality, using the @EnableSpringDataWebSupport annotation. Add this annotation to your configuration class and it will add some paging and sorting functionality to your web application that we’ll get into next. Now that we have our repository, we’ll create a controller.
This controller currently only contains one request mapping. Already you should be able to see where the pagination comes in. The method accepts two arguments, a
Pageable and a
PagedResourcesAssembler. Both of these will be injected thanks to the
@EnableSpringDataWebSupport annotation that you should have added to your configuration class. A
Pageable will consist of a
sort property. Put simpler, if you have a query string that looks like
?page=0&size=20&sort=type,desc, this will be converted to a
Pageable with page 0, 20 records per page, and sorting on the type property in descending order. The
PagedResourcesAssembler will be used to convert the
Widget entities into a
PagedResources instance which contains not only the underlying data, but also some metadata like the current page number, total number of pages, total number of records, page size, etc.
PagedResources will also contain links for previous and next pages if you desire to use them. A
PagedResources is part of the Spring HATEOAS project, so you’ll need that on your classpath.
You may have noticed that I’m also injecting a
WidgetResourceAssembler and then later using it when converting the paged widgets into a
PagedResources instance. This is so I can add links to the underlying
Resource objects referencing the entities themselves. For some reason, simply converting the paged data to a
PagedResources resulted in each
Resource not having any links.
That’s pretty well all that’s needed on the backend for making paged queries and using them in your controller.
Enter AngularJS and a custom paging filter
Now we’ll focus on the front end with an AngularJS app. If you’ve read some of my other posts, you might have noticed I like using Yeoman. So the front end of this app was built using the AngularJS Yeoman generator.
The first thing we’ll look at in our Angular app is the widget controller which will be responsible for making requests to our backend for widgets.
There’s a bit going on here, but I’ll break it down. I’ll assume you already know about AngularJS, so won’t go into the Angular specific details. Using Angular’s
$location service, we’ll retrieve the
search property and use it to get the query parameters. Since an Angular app is a single page app, there won’t be any page refresh between requests, so we’ll mimic the paged requests in the query string, capture them and use them to request paged data from our backend. You’ll see I’m also adding some default values in the event a query parameter is missing, so page will be
0 by default, size will be
20 by default and sort will be
type,desc by default.
Next we’re requesting the data from our backend. If successful, the content, page and links are assigned to the
$scope. Those three properties are what a
PagedResources instance in Spring Data contains. We’re also adding the current sort value to the scope so we can append it to any link in the view attached to this controller. If an error was encountered requesting the data, then we’re merely adding that error to the
Next comes the view.
What we have here is a typical HTML list, but there’s a lot going on here too. The first list item is used to link to the first page. Next list item links to the previous page. The last list item is used to link to the last page. The next to last list item is used to link to the next page. The real fun is in the third list item. You’ll notice I’m using
ng-repeat to iterate over an array. The purpose of this is to create quick page links for the pages surrounding the current page. The final result might look something like this.
As you can see, aside from the first, prev, next and last links, we have 10 inter page links as well. We achieved this by iterating over an array and passing it through a custom paging filter.
The math looks a bit complex and I’ll admit, it threw me for a bit of a loop while trying to figure it out, and there’s maybe even a simpler way to do it, but the gist of it is, we take the current page number, total number of pages and a range of pages we want links for. The range is not the amount of pages we want, but rather, from the current page, how many pages to the right and left do we want. The
maxPage variables are used to intelligently determine if we’re near the beginning or end of the list, then only add as many pages as we need on either side so we don’t go past 0 or the total number of pages, but also make sure we always have a number of page links equal to the range x 2, or in this example, 10 since we passed a 5 to the range argument.
To conclude, I hope you saw how we can nicely marry Spring Data’s pagination support with AngularJS to create user friendly pagination links.