Validação de formulário com AngularJS e Spring MVC

Validação de formulário com AngularJS e Spring MVC

1. Visão geral

A validação nunca é tão direta quanto esperamos. E, é claro, validar os valores inseridos por um usuário em um aplicativo é muito importante para preservar a integridade de nossos dados.

No contexto de um aplicativo Web, a entrada de dados geralmente é feita usando formulários HTML e requer validação do lado do cliente e do servidor.

Neste tutorial, daremos uma olhada emimplementing client-side validation of form input using AngularJS and server-side validation using the Spring MVC framework.

2. Dependências do Maven

Para começar, vamos adicionar as seguintes dependências:


    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

As versões mais recentes despring-webmvc,hibernate-validatorejackson-databind podem ser baixadas do Maven Central.

3. Validação usando o Spring MVC

Um aplicativo nunca deve confiar apenas na validação do lado do cliente, pois isso pode ser facilmente contornado. Para impedir que valores incorretos ou mal-intencionados sejam salvos ou causem execução inadequada da lógica do aplicativo, é importante validar valores de entrada também no lado do servidor.

Spring MVC oferece suporte para validação do lado do servidor usando anotações de especificaçãoJSR 349 Bean Validation. Para este exemplo, usaremos a implementação de referência da especificação, que éhibernate-validator.

3.1. O Modelo de Dados

Vamos criar uma classeUser que tem propriedades anotadas com anotações de validação apropriadas:

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
}

As anotações usadas acima pertencem à especificaçãoJSR 349, com exceção de@Emaile@NotBlank, que são específicas da bibliotecahibernate-validator.

3.2. Spring MVC Controller

Vamos criar uma classe de controlador que define um endpoint/user, que será usado para salvar um novo objetoUser em umList.

Para possibilitar a validação do objetoUser recebido através dos parâmetros da requisição, a declaração deve ser precedida da anotação@Valid, e os erros de validação serão mantidos em uma instânciaBindingResult.

Para determinar se o objeto contém valores inválidos, podemos usar o métodohasErrors() deBindingResult.

SehasErrors() retornartrue, podemos retornar umJSON array contendo as mensagens de erro associadas às validações que não foram aprovadas. Caso contrário, adicionaremos o objeto à 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 você pode ver,server-side validation adds the advantage of having the ability to perform additional checks that are not possible on the client side.

No nosso caso, podemos verificar se já existe um usuário com o mesmo e-mail - e retornar um status de 409 CONFLICT se for esse o caso.

Também precisamos definir nossa lista de usuários e inicializá-la com alguns 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));

Vamos também adicionar um mapeamento para recuperar a lista de usuários como um objeto JSON:

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

O item final que precisamos em nosso controlador Spring MVC é um mapeamento para retornar a página principal do nosso aplicativo:

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

Vamos dar uma olhada na páginauser.html em mais detalhes na seção AngularJS.

3.3. Configuração do Spring MVC

Vamos adicionar uma configuração MVC básica ao nosso aplicativo:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.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 o aplicativo

Vamos criar uma classe que implemente a interfaceWebApplicationInitializer para executar nosso aplicativo:

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. Testando a validação Spring Mvc usando Curl

Antes de implementar a seção do cliente AngularJS, podemos testar nossa API usando cURL com o comando:

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

A resposta é uma matriz que contém as mensagens de erro padrão:

[
    "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. Validação do AngularJS

A validação do lado do cliente é útil na criação de uma melhor experiência do usuário, pois fornece ao usuário informações sobre como enviar com êxito dados válidos e permite que ele continue a interagir com o aplicativo.

A biblioteca AngularJS oferece excelente suporte para adicionar requisitos de validação em campos de formulário, manipular mensagens de erro e criar formulários válidos e inválidos.

Primeiro, vamos criar um módulo AngularJS que injeta o módulongMessages, que é usado para mensagens de validação:

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

A seguir, vamos criar um serviço e controlador AngularJS que consumirá a API construída na seção anterior.

4.1. O serviço AngularJS

Nosso serviço terá dois métodos que chamam os métodos do controlador MVC - um para salvar um usuário e outro para recuperar a lista de usuários:

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. O Controlador AngularJS

O controladorUserCtrl injetaUserService, chama os métodos de serviço e lida com a resposta e as mensagens de erro:

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 no exemplo acima que o método de serviço é chamado apenas se a propriedade$valid deuserForm for verdadeira. Ainda, neste caso, existe a verificação adicional de emails duplicados, que só pode ser feita no servidor e é tratada separadamente na funçãoerror().

Além disso, observe que há uma variávelsubmitted definida que nos dirá se o formulário foi enviado ou não.

Inicialmente, essa variável seráfalse, e na invocação do métodosaveUser(), ela se tornatrue. Se não quisermos que as mensagens de validação sejam exibidas antes de o usuário enviar o formulário, podemos usar a variávelsubmitted para evitar isso.

4.3. Formulário usando validação AngularJS

Para usar a biblioteca AngularJS e nosso módulo AngularJS, precisaremos adicionar os scripts à nossa páginauser.html:



Então, podemos usar nosso módulo e controlador definindo as propriedadesng-appeng-controller:

Vamos criar nosso formulário HTML:

...

Observe que temos que definir o atributonovalidate no formulário para evitar a validação padrão do HTML5 e substituí-lo pelo nosso.

O atributong-class adiciona a classe CSSform-error dinamicamente ao formulário se a variávelsubmitted tiver um valor detrue.

O atributong-submit define a função do controlador AngularJS que será chamada quando o formulário for enviado. Usarng-submit em vez deng-click tem a vantagem de também responder ao envio do formulário usando a tecla ENTER.

Agora vamos adicionar os quatro campos de entrada para os atributos do usuário:











Cada campo de entrada possui uma ligação a uma propriedade da variáveluser por meio do atributong-model.

For setting validation rules, usamos o atributo HTML5required e vários atributos específicos do AngularJS:ng-minglength, ng-maxlength, ng-min,eng-trim.

Para o campoemail, também usamos o atributotype com um valor deemail para validação de email do lado do cliente.

In order to add error messages corresponding to each field, AngularJS oferece a diretivang-messages, que percorre um objeto$errors de entrada e exibe mensagens com base em cada regra de validação.

Vamos adicionar a diretiva para o campoemail logo após a definição de entrada:

Invalid email!

Email is required!

Mensagens de erro semelhantes podem ser adicionadas para os outros campos de entrada.

We can control when the directive is displayed para o campoemail usando a propriedadeng-show com uma expressão booleana. Em nosso exemplo, exibimos a diretiva quando o campo tem um valor inválido, o que significa que a propriedade$invalid étrue, e a variávelsubmitted também étrue.

Apenas uma mensagem de erro será exibida por vez para um campo.

Também podemos adicionar um sinal de marca de seleção (representado pelo caractere de código HEX ✓) após o campo de entrada, caso o campo seja válido, dependendo da propriedade$valid:

A validação do AngularJS também oferece suporte para estilização usando classes CSS comong-valideng-invalid ou mais específicas comong-invalid-requiredeng-invalid-minlength.

Vamos adicionar a propriedade CSSborder-color:red para entradas inválidas dentro da classeform-error do formulário:

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

Também podemos mostrar as mensagens de erro em vermelho usando uma classe CSS:

.error-messages {
    color:red;
}

Depois de colocar tudo junto, vamos ver um exemplo de como nossa validação de formulário do lado do cliente ficará quando preenchida com uma mistura de valores válidos e inválidos:

AngularJS form validation example

5. Conclusão

Neste tutorial, mostramos como podemos combinar validação do lado do cliente e do lado do servidor usando AngularJS e Spring MVC.

Como sempre, o código-fonte completo dos exemplos pode ser encontradoover on GitHub.

Para visualizar o aplicativo, acesse a URL/userPage após executá-lo.