Angular-datatables with server side pagination using Spring Data

We are using angular-datatables in a project. So far, we just returned all entities from the server’s REST controller (Using Spring Boot and Spring Data on the server side). I wanted to see how I could implement server side pagination to avoid returning all records at once.

I was lucky to find spring-data-jpa-datatables which makes it very easy to do.

First, add the dependency in your pom.xml:

        <dependency>
            <groupId>com.github.darrachequesne</groupId>
            <artifactId>spring-data-jpa-datatables</artifactId>
            <version>2.1</version>
        </dependency>

In my UserController, I have currently this:

    @RequestMapping(method = RequestMethod.GET)
    @Secured(Roles.ADMIN)
    public List<UserDto> getUsers() {
        return StreamSupport.stream(userRepository.findAll().spliterator(), false)
                            .map(UserDto::fromUser)
                            .collect(Collectors.toList());
    }

I added a new method that would be used for the server side pagination and searching (I could have replaced the old method as well, as it will no longer be used):

    @RequestMapping(value = "/datatables-view", method = RequestMethod.POST)
    @Secured(Roles.ADMIN)
    public DataTablesOutput<UserDto> getUsersForDatatables(@Valid @RequestBody DataTablesInput input) {
        DataTablesOutput<User> usersTest = userRepository.findAll(input);

        if( usersTest.getError() != null ) {
            throw new IllegalArgumentException(usersTest.getError());
        }

        DataTablesOutput<UserDto> result = new DataTablesOutput<>();
        result.setData(usersTest.getData().stream().map( UserDto::fromUser ).collect(Collectors.toList()));
        result.setDraw(usersTest.getDraw());
        result.setError(usersTest.getError());
        result.setRecordsFiltered(usersTest.getRecordsFiltered());
        result.setRecordsTotal(usersTest.getRecordsTotal());
        return result;
    }

To support this controller, you need to update the UserRepository to extend from DataTablesRepository, so change this:

  public interface UserRepository extends CrudRepository<User, UserId>, UserRepositoryCustom {

to

  public interface UserRepository extends DataTablesRepository<User, UserId>, UserRepositoryCustom {

On the client side, I had this code:

  $scope.dtOptions = DTOptionsBuilder.fromFnPromise(function(){
            return Users.query().$promise;
        })
        .withBootstrap()
        .withPaginationType('simple_numbers')
        .withDisplayLength(20)
        .withOption('createdRow', function (row) {
            // Recompiling so we can bind Angular directive to the DT
            $compile(angular.element(row).contents())($scope);
        })
        .withOption('saveState', true)
        .withOption('order', [0, 'asc']);

        // Datatables columns builder
        $scope.dtColumns = [ .. ] //column definitions here

This code now changes to:

$scope.dtOptions = DTOptionsBuilder.newOptions()
            .withOption('ajax', {
                contentType: 'application/json',
                url: '/api/users/datatables-view',
                type: 'POST',
                beforeSend: function(xhr){
                    xhr.setRequestHeader("Authorization",
                        "Bearer " + AuthenticationService.getAccessToken());
                },
                data: function(data, dtInstance) {
                    
                    // The returned object has 'email' as property, but the server entity has 'emailAddress'
                    // We need to override what we ask to the server here otherwise search will not work
                    data.columns[1].data = "emailAddress";

                    // Any values you set on the data object will be passed along as parameters to the server
                    //data.access_token = AuthenticationService.getAccessToken();
                    return JSON.stringify(data);
                }
            })
            .withDataProp('data') // This is the name of the value in the returned recordset which contains the actual data
            .withOption('serverSide', true)
        .withBootstrap()
        .withPaginationType('simple_numbers')
        .withDisplayLength(20)
        .withOption('createdRow', function (row) {
            // Recompiling so we can bind Angular directive to the DT
            $compile(angular.element(row).contents())($scope);
        })
        .withOption('saveState', true)
        .withOption('order', [0, 'asc']);

The most important things are:

  • Set the contentType so that we send JSON to the REST controller
  • Set the url that points to our new controller method
  • Set the type to POST since we accept a POST in the controller
  • Add a beforeSend function to set the Authorization header so we can access the controller method that is secured with Spring Security
  • Add a data function to return the data object as JSON

One thing I had to do additionally is this line:

data.columns[1].data = "emailAddress";

The reason for this is that I return UserDto objects from the controller which have email as a property and thus the columns are defined like that in JavaScript. However, the real User entity on the server uses emailAddress as property. With this line, the server side code will use the good property for searching and sorting.

After all this, you can check in the developer console to the requests and responses, only the actual needed data will be returned from the server. The search box will work and also the sorting will work.

What is also very nice is that the pagination adapts perfectly. And when you start to search, it also shows this in the footer:

Showing 1 to 9 of 9 entries (filtered from 24 total entries)

And that is all you need to get pagination and sorting with server-side processing to handle large data sets using AngularJS, Datatables and Spring.

This know-how originated during the development of a PegusApps project.

Advertisements

One thought on “Angular-datatables with server side pagination using Spring Data

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s