Validación de formularios con AngularJS y Spring MVC

1. Información general

La validación nunca es tan sencilla como esperamos. Y, por supuesto, validar los valores ingresados ​​por un usuario en una aplicación es muy importante para preservar la integridad de nuestros datos.

En el contexto de una aplicación web, la entrada de datos generalmente se realiza mediante formularios HTML y requiere validación tanto del lado del cliente como del lado del servidor.

En este tutorial, veremos cómo implementar la validación del lado del cliente de la entrada del formulario usando AngularJS y la validación del lado del servidor usando el marco Spring MVC .

Este artículo se centra en Spring MVC. Nuestro artículo Validación en Spring Boot describe cómo realizar validaciones en Spring Boot.

2. Dependencias de Maven

Para empezar, agreguemos las siguientes dependencias:

 org.springframework spring-webmvc 4.3.7.RELEASE   org.hibernate hibernate-validator 5.4.0.Final   com.fasterxml.jackson.core jackson-databind 2.8.7 

Las últimas versiones de spring-webmvc, hibernate-validator y jackson-databind se pueden descargar desde Maven Central.

3. Validación con Spring MVC

Una aplicación nunca debe depender únicamente de la validación del lado del cliente, ya que esto se puede eludir fácilmente. Para evitar que se guarden valores incorrectos o maliciosos o que provoquen una ejecución incorrecta de la lógica de la aplicación, también es importante validar los valores de entrada en el lado del servidor.

Spring MVC ofrece soporte para la validación del lado del servidor mediante el uso de anotaciones de especificación JSR 349 ​​Bean Validation . Para este ejemplo, usaremos la implementación de referencia de la especificación, que es hibernate-validator .

3.1. El modelo de datos

Creemos una clase de usuario que tenga propiedades anotadas con anotaciones de validación apropiadas:

public class User { @NotNull @Email private String email; @NotNull @Size(min = 4, max = 15) private String password; @NotBlank private String name; @Min(18) @Digits(integer = 2, fraction = 0) private int age; // standard constructor, getters, setters }

Las anotaciones utilizadas anteriormente pertenecen a la especificación JSR 349 , con la excepción de @Email y @NotBlank , que son específicas de la biblioteca hibernate-validator .

3.2. Controlador Spring MVC

Creemos una clase de controlador que defina un punto final / usuario , que se utilizará para guardar un nuevo objeto Usuario en una Lista .

Para habilitar la validación del objeto Usuario recibido a través de los parámetros de solicitud, la declaración debe estar precedida por la anotación @Valid , y los errores de validación se mantendrán en una instancia de BindingResult .

Para determinar si el objeto contiene valores no válidos, podemos usar el método hasErrors () de BindingResult .

Si hasErrors () devuelve verdadero , podemos devolver una matriz JSON que contiene los mensajes de error asociados con las validaciones que no pasaron. De lo contrario, agregaremos el objeto a la lista:

@PostMapping(value = "/user") @ResponseBody public ResponseEntity saveUser(@Valid User user, BindingResult result, Model model) { if (result.hasErrors()) { List errors = result.getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.toList()); return new ResponseEntity(errors, HttpStatus.OK); } else { if (users.stream().anyMatch(it -> user.getEmail().equals(it.getEmail()))) { return new ResponseEntity( Collections.singletonList("Email already exists!"), HttpStatus.CONFLICT); } else { users.add(user); return new ResponseEntity(HttpStatus.CREATED); } } }

Como puede ver, la validación del lado del servidor agrega la ventaja de tener la capacidad de realizar verificaciones adicionales que no son posibles en el lado del cliente.

En nuestro caso, podemos verificar si ya existe un usuario con el mismo correo electrónico, y devolver un estado de CONFLICTO 409 si ese es el caso.

También necesitamos definir nuestra lista de usuarios e inicializarla con algunos valores:

private List users = Arrays.asList( new User("[email protected]", "pass", "Ana", 20), new User("[email protected]", "pass", "Bob", 30), new User("[email protected]", "pass", "John", 40), new User("[email protected]", "pass", "Mary", 30));

Agreguemos también una asignación para recuperar la lista de usuarios como un objeto JSON:

@GetMapping(value = "/users") @ResponseBody public List getUsers() { return users; }

El elemento final que necesitamos en nuestro controlador Spring MVC es un mapeo para devolver la página principal de nuestra aplicación:

@GetMapping("/userPage") public String getUserProfilePage() { return "user"; }

Echaremos un vistazo a la página user.html con más detalle en la sección AngularJS.

3.3. Configuración de Spring MVC

Agreguemos una configuración básica de MVC a nuestra aplicación:

@Configuration @EnableWebMvc @ComponentScan(basePackages = "com.baeldung.springmvcforms") class ApplicationConfiguration implements WebMvcConfigurer { @Override public void configureDefaultServletHandling( DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Bean public InternalResourceViewResolver htmlViewResolver() { InternalResourceViewResolver bean = new InternalResourceViewResolver(); bean.setPrefix("/WEB-INF/html/"); bean.setSuffix(".html"); return bean; } }

3.4. Inicializando la Aplicación

Creemos una clase que implemente la interfaz WebApplicationInitializer para ejecutar nuestra aplicación:

public class WebInitializer implements WebApplicationInitializer { public void onStartup(ServletContext container) throws ServletException { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(ApplicationConfiguration.class); ctx.setServletContext(container); container.addListener(new ContextLoaderListener(ctx)); ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx)); servlet.setLoadOnStartup(1); servlet.addMapping("/"); } }

3.5. Prueba de la validación de Spring Mvc con Curl

Antes de implementar la sección del cliente AngularJS, podemos probar nuestra API usando cURL con el comando:

curl -i -X POST -H "Accept:application/json" "localhost:8080/spring-mvc-forms/user?email=aaa&password=12&age=12"

La respuesta es una matriz que contiene los mensajes de error predeterminados:

[ "not a well-formed email address", "size must be between 4 and 15", "may not be empty", "must be greater than or equal to 18" ]

4. Validación de AngularJS

La validación del lado del cliente es útil para crear una mejor experiencia de usuario, ya que proporciona al usuario información sobre cómo enviar datos válidos con éxito y les permite poder continuar interactuando con la aplicación.

La biblioteca AngularJS tiene un gran soporte para agregar requisitos de validación en campos de formulario, manejar mensajes de error y diseñar formularios válidos e inválidos.

Primero, creemos un módulo AngularJS que inyecta el módulo ngMessages , que se usa para los mensajes de validación:

var app = angular.module('app', ['ngMessages']);

A continuación, creemos un servicio y controlador AngularJS que consumirá la API construida en la sección anterior.

4.1. El servicio AngularJS

Nuestro servicio tendrá dos métodos que llaman a los métodos del controlador MVC: uno para guardar un usuario y otro para recuperar la lista de usuarios:

app.service('UserService',['$http', function ($http) { this.saveUser = function saveUser(user){ return $http({ method: 'POST', url: 'user', params: {email:user.email, password:user.password, name:user.name, age:user.age}, headers: 'Accept:application/json' }); } this.getUsers = function getUsers(){ return $http({ method: 'GET', url: 'users', headers:'Accept:application/json' }).then( function(response){ return response.data; } ); } }]);

4.2. El controlador AngularJS

El controlador UserCtrl inyecta UserService , llama a los métodos de servicio y maneja la respuesta y los mensajes de error:

app.controller('UserCtrl', ['$scope','UserService', function ($scope,UserService) { $scope.submitted = false; $scope.getUsers = function() { UserService.getUsers().then(function(data) { $scope.users = data; }); } $scope.saveUser = function() { $scope.submitted = true; if ($scope.userForm.$valid) { UserService.saveUser($scope.user) .then (function success(response) { $scope.message = 'User added!'; $scope.errorMessage = ''; $scope.getUsers(); $scope.user = null; $scope.submitted = false; }, function error(response) { if (response.status == 409) { $scope.errorMessage = response.data.message; } else { $scope.errorMessage = 'Error adding user!'; } $scope.message = ''; }); } } $scope.getUsers(); }]);

Podemos ver en el ejemplo anterior que se llama al método de servicio solo si la propiedad $ valid de userForm es verdadera. Aún así, en este caso, existe la verificación adicional de correos electrónicos duplicados, que solo se puede hacer en el servidor y se maneja por separado en la función error () .

Además, observe que hay una variable enviada definida que nos dirá si el formulario ha sido enviado o no.

Inicialmente, esta variable será falsa y al invocar el método saveUser () , se volverá verdadera . Si no queremos que se muestren mensajes de validación antes de que el usuario envíe el formulario, podemos usar la variable enviada para evitarlo.

4.3. Form Using AngularJS Validation

In order to make use of the AngularJS library and our AngularJS module, we will need to add the scripts to our user.html page:

Then we can use our module and controller by setting the ng-app and ng-controller properties:

Let's create our HTML form:

 ... 

Note that we have to set the novalidate attribute on the form in order to prevent default HTML5 validation and replace it with our own.

The ng-class attribute adds the form-error CSS class dynamically to the form if the submitted variable has a value of true.

The ng-submit attribute defines the AngularJS controller function that will be called when the form in submitted. Using ng-submit instead of ng-click has the advantage that it also responds to submitting the form using the ENTER key.

Now let's add the four input fields for the User attributes:

Email:  Password:  Name:  Age: 

Each input field has a binding to a property of the user variable through the ng-model attribute.

For setting validation rules, we use the HTML5 required attribute and several AngularJS-specific attributes: ng-minglength, ng-maxlength, ng-min, and ng-trim.

For the email field, we also use the type attribute with a value of email for client-side email validation.

In order to add error messages corresponding to each field, AngularJS offers the ng-messages directive, which loops through an input's $errors object and displays messages based on each validation rule.

Let's add the directive for the email field right after the input definition:

Invalid email!

Email is required!

Similar error messages can be added for the other input fields.

We can control when the directive is displayed for the email field using the ng-show property with a boolean expression. In our example, we display the directive when the field has an invalid value, meaning the $invalid property is true, and the submitted variable is also true.

Only one error message will be displayed at a time for a field.

We can also add a check mark sign (represented by HEX code character ✓) after the input field in case the field is valid, depending on the $valid property:

AngularJS validation also offers support for styling using CSS classes such as ng-valid and ng-invalid or more specific ones like ng-invalid-required and ng-invalid-minlength.

Let's add the CSS property border-color:red for invalid inputs inside the form's form-error class:

.form-error input.ng-invalid { border-color:red; }

We can also show the error messages in red using a CSS class:

.error-messages { color:red; }

After putting everything together, let's see an example of how our client-side form validation will look when filled out with a mix of valid and invalid values:

5. Conclusion

In this tutorial, we've shown how we can combine client-side and server-side validation using AngularJS and Spring MVC.

Como siempre, el código fuente completo de los ejemplos se puede encontrar en GitHub.

Para ver la aplicación, acceda a la URL / userPage después de ejecutarla.