Impaginazione con la tabella Spring REST e AngularJS

REST Top

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO

1. Panoramica

In questo articolo, ci concentreremo principalmente sull'implementazione dell'impaginazione lato server in un'API REST Spring e in un semplice front-end AngularJS.

Esploreremo anche una griglia di tabella comunemente usata in Angular denominata UI Grid.

2. Dipendenze

Qui descriviamo in dettaglio le varie dipendenze richieste per questo articolo.

2.1. JavaScript

Affinché Angular UI Grid funzioni, avremo bisogno degli script seguenti importati nel nostro HTML.

  • JS angolare (1.5.8)
  • Griglia UI angolare

2.2. Esperto di

Per il nostro backend useremo Spring Boot , quindi avremo bisogno delle seguenti dipendenze:

 org.springframework.boot spring-boot-starter-web   org.springframework.boot spring-boot-starter-tomcat provided 

Nota: altre dipendenze non sono state specificate qui, per l'elenco completo, controlla il pom.xml completo nel progetto GitHub.

3. Informazioni sull'applicazione

L'applicazione è una semplice app directory dello studente che consente agli utenti di vedere i dettagli dello studente in una griglia di tabelle impaginate.

L'applicazione utilizza Spring Boot e viene eseguita in un server Tomcat incorporato con un database incorporato.

Infine, dal punto di vista dell'API, ci sono alcuni modi per eseguire l'impaginazione, descritti nell'articolo REST Pagination in Spring qui - che è altamente consigliato leggere insieme a questo articolo.

La nostra soluzione qui è semplice: avere le informazioni di paging in una query URI come segue: / student / get? Page = 1 & size = 2 .

4. Il lato client

Innanzitutto, dobbiamo creare la logica lato client.

4.1. La UI-Grid

Il nostro index.html avrà le importazioni di cui abbiamo bisogno e una semplice implementazione della griglia della tabella:

Diamo uno sguardo più da vicino al codice:

  • ng-app - è la direttiva Angular che carica l' app del modulo . Tutti gli elementi sottostanti faranno parte del modulo dell'app
  • ng-controller - è la direttiva Angular che carica il controller StudentCtrl con un alias di vm. Tutti gli elementi sotto di questi faranno parte del controller StudentCtrl
  • ui-grid : è la direttiva Angular che appartiene a Angular ui-grid e utilizza gridOptions come impostazioni predefinite, gridOptions è dichiarato sotto $ scope in app.js

4.2. Il modulo AngularJS

Definiamo prima il modulo in app.js :

var app = angular.module('app', ['ui.grid','ui.grid.pagination']);

Abbiamo dichiarato il modulo app e abbiamo inserito ui.grid per abilitare la funzionalità UI-Grid; abbiamo anche inserito ui.grid.pagination per abilitare il supporto dell'impaginazione .

Successivamente, definiremo il controller:

app.controller('StudentCtrl', ['$scope','StudentService', function ($scope, StudentService) { var paginationOptions = { pageNumber: 1, pageSize: 5, sort: null }; StudentService.getStudents( paginationOptions.pageNumber, paginationOptions.pageSize).success(function(data){ $scope.gridOptions.data = data.content; $scope.gridOptions.totalItems = data.totalElements; }); $scope.gridOptions = { paginationPageSizes: [5, 10, 20], paginationPageSize: paginationOptions.pageSize, enableColumnMenus:false, useExternalPagination: true, columnDefs: [ { name: 'id' }, { name: 'name' }, { name: 'gender' }, { name: 'age' } ], onRegisterApi: function(gridApi) { $scope.gridApi = gridApi; gridApi.pagination.on.paginationChanged( $scope, function (newPage, pageSize) { paginationOptions.pageNumber = newPage; paginationOptions.pageSize = pageSize; StudentService.getStudents(newPage,pageSize) .success(function(data){ $scope.gridOptions.data = data.content; $scope.gridOptions.totalItems = data.totalElements; }); }); } }; }]); 

Diamo ora un'occhiata alle impostazioni di impaginazione personalizzate in $ scope.gridOptions :

  • paginationPageSizes : definisce le opzioni disponibili per le dimensioni della pagina
  • paginationPageSize : definisce la dimensione della pagina predefinita
  • enableColumnMenus - viene utilizzato per abilitare / disabilitare il menu sulle colonne
  • useExternalPagination : è obbligatorio se stai impaginando sul lato server
  • columnDefs : i nomi delle colonne che verranno automaticamente mappati all'oggetto JSON restituito dal server. I nomi dei campi nell'oggetto JSON restituiti dal server e il nome della colonna definito devono corrispondere.
  • onRegisterApi - la capacità di registrare eventi di metodi pubblici all'interno della griglia. Qui abbiamo registrato gridApi.pagination.on.paginationChanged per dire a UI-Grid di attivare questa funzione ogni volta che la pagina veniva modificata.

E per inviare la richiesta all'API:

app.service('StudentService',['$http', function ($http) { function getStudents(pageNumber,size) { pageNumber = pageNumber > 0?pageNumber - 1:0; return $http({ method: 'GET', url: 'student/get?page='+pageNumber+'&size='+size }); } return { getStudents: getStudents }; }]);

5. Il backend e l'API

5.1. Il servizio RESTful

Ecco la semplice implementazione dell'API RESTful con supporto per l'impaginazione:

@RestController public class StudentDirectoryRestController { @Autowired private StudentService service; @RequestMapping( value = "/student/get", params = { "page", "size" }, method = RequestMethod.GET ) public Page findPaginated( @RequestParam("page") int page, @RequestParam("size") int size) { Page resultPage = service.findPaginated(page, size); if (page > resultPage.getTotalPages()) { throw new MyResourceNotFoundException(); } return resultPage; } }

La @RestController è stato introdotto nella primavera del 4.0 come un'annotazione convenienza che dichiara implicitamente @Controller e @ResponseBody.

Per la nostra API, abbiamo dichiarato che accetta due parametri che sono pagina e dimensione che determinerebbero anche il numero di record da restituire al client.

Abbiamo anche aggiunto una semplice convalida che genererà un'eccezione MyResourceNotFoundException se il numero di pagina è maggiore delle pagine totali.

Infine, restituiremo Page come risposta: questo è un componente estremamente utile di S pring Data che ha mantenuto i dati di impaginazione.

5.2. L'implementazione del servizio

Il nostro servizio restituirà semplicemente i record in base alla pagina e alle dimensioni fornite dal controller:

@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentRepository dao; @Override public Page findPaginated(int page, int size) { return dao.findAll(new PageRequest(page, size)); } } 

5.3. L'implementazione del repository

Per il nostro livello di persistenza, utilizziamo un database incorporato e Spring Data JPA.

Per prima cosa, dobbiamo impostare la nostra configurazione di persistenza:

@EnableJpaRepositories("com.baeldung.web.dao") @ComponentScan(basePackages = { "com.baeldung.web" }) @EntityScan("com.baeldung.web.entity") @Configuration public class PersistenceConfig { @Bean public JdbcTemplate getJdbcTemplate() { return new JdbcTemplate(dataSource()); } @Bean public DataSource dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); EmbeddedDatabase db = builder .setType(EmbeddedDatabaseType.HSQL) .addScript("db/sql/data.sql") .build(); return db; } } 

La configurazione della persistenza è semplice: abbiamo @EnableJpaRepositories per eseguire la scansione del pacchetto specificato e trovare le nostre interfacce di repository JPA Spring Data.

Abbiamo qui @ComponentScan per scansionare automaticamente tutti i bean e @EntityScan (da Spring Boot) per scansionare classi di entità.

Abbiamo anche dichiarato la nostra semplice origine dati, utilizzando un database incorporato che eseguirà lo script SQL fornito all'avvio.

Ora è il momento di creare il nostro repository di dati:

public interface StudentRepository extends JpaRepository {} 

This is basically all that we need to do here; if you want to go deeper into how to set up and use the highly powerful Spring Data JPA, definitely read the guide to it here.

6. Pagination Request and Response

When calling the API – //localhost:8080/student/get?page=1&size=5, the JSON response will look something like this:

{ "content":[ {"studentId":"1","name":"Bryan","gender":"Male","age":20}, {"studentId":"2","name":"Ben","gender":"Male","age":22}, {"studentId":"3","name":"Lisa","gender":"Female","age":24}, {"studentId":"4","name":"Sarah","gender":"Female","age":26}, {"studentId":"5","name":"Jay","gender":"Male","age":20} ], "last":false, "totalElements":20, "totalPages":4, "size":5, "number":0, "sort":null, "first":true, "numberOfElements":5 } 

One thing to notice here is that server returns a org.springframework.data.domain.Page DTO, wrapping our Student Resources.

The Page object will have the following fields:

  • last – set to true if its the last page otherwise false
  • first – set to true if it's the first page otherwise false
  • totalElements – the total number of rows/records. In our example, we passed this to the ui-grid options $scope.gridOptions.totalItems to determine how many pages will be available
  • totalPages – the total number of pages which was derived from (totalElements / size)
  • size – the number of records per page, this was passed from the client via param size
  • number – the page number sent by the client, in our response the number is 0 because in our backend we are using an array of Students which is a zero-based index, so in our backend, we decrement the page number by 1
  • sort – the sorting parameter for the page
  • numberOfElements – the number of rows/records return for the page

7. Testing Pagination

Let's now set up a test for our pagination logic, using RestAssured; to learn more about RestAssured you can have a look at this tutorial.

7.1. Preparing the Test

For ease of development of our test class we will be adding the static imports:

io.restassured.RestAssured.* io.restassured.matcher.RestAssuredMatchers.* org.hamcrest.Matchers.*

Next, we'll set up the Spring enabled test:

@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration @IntegrationTest("server.port:8888") 

The @SpringApplicationConfiguration helps Spring know how to load the ApplicationContext, in this case, we used the Application.java to configure our ApplicationContext.

The @WebAppConfiguration was defined to tell Spring that the ApplicationContext to be loaded should be a WebApplicationContext.

And the @IntegrationTest was defined to trigger the application startup when running the test, this makes our REST services available for testing.

7.2. The Tests

Here is our first test case:

@Test public void givenRequestForStudents_whenPageIsOne_expectContainsNames() { given().params("page", "0", "size", "2").get(ENDPOINT) .then() .assertThat().body("content.name", hasItems("Bryan", "Ben")); } 

This test case above is to test that when page 1 and size 2 is passed to the REST service the JSON content returned from the server should have the names Bryan and Ben.

Let's dissect the test case:

  • given – the part of RestAssured and is used to start building the request, you can also use with()
  • get – the part of RestAssured and if used triggers a get request, use post() for post request
  • hasItems – the part of hamcrest that checks if the values have any match

We add a few more test cases:

@Test public void givenRequestForStudents_whenResourcesAreRetrievedPaged_thenExpect200() { given().params("page", "0", "size", "2").get(ENDPOINT) .then() .statusCode(200); }

This test asserts that when the point is actually called an OK response is received:

@Test public void givenRequestForStudents_whenSizeIsTwo_expectNumberOfElementsTwo() { given().params("page", "0", "size", "2").get(ENDPOINT) .then() .assertThat().body("numberOfElements", equalTo(2)); }

This test asserts that when page size of two is requested the pages size that is returned is actually two:

@Test public void givenResourcesExist_whenFirstPageIsRetrieved_thenPageContainsResources() { given().params("page", "0", "size", "2").get(ENDPOINT) .then() .assertThat().body("first", equalTo(true)); } 

This test asserts that when the resources are called the first time the first page name value is true.

There are many more tests in the repository, so definitely have a look at the GitHub project.

8. Conclusion

This article illustrated how to implement a data table grid using UI-Grid in AngularJS and how to implement the required server side pagination.

L'implementazione di questi esempi e test può essere trovata nel progetto GitHub. Questo è un progetto Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.

Per eseguire il progetto Spring boot, puoi semplicemente eseguire mvn spring-boot: eseguirlo e accedervi localmente su // localhost: 8080 /.

REST fondo

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO