<dependency>
<groupId>com.github.darrachequesne</groupId>
<artifactId>spring-data-jpa-datatables</artifactId>
<version>2.1</version>
</dependency>
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.