Validation de formulaire avec AngularJS et Spring MVC

Validation de formulaire avec AngularJS et Spring MVC

1. Vue d'ensemble

La validation n’est jamais aussi simple que nous le prévoyons. Et bien sûr, la validation des valeurs saisies par un utilisateur dans une application est très importante pour préserver l'intégrité de nos données.

Dans le contexte d'une application Web, la saisie de données se fait généralement à l'aide de formulaires HTML et nécessite une validation à la fois côté client et côté serveur.

Dans ce tutoriel, nous allons jeter un œil àimplementing client-side validation of form input using AngularJS and server-side validation using the Spring MVC framework.

2. Dépendances Maven

Pour commencer, ajoutons les dépendances suivantes:


    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

Les dernières versions despring-webmvc,hibernate-validator etjackson-databind peuvent être téléchargées depuis Maven Central.

3. Validation à l'aide de Spring MVC

Une application ne doit jamais compter uniquement sur la validation côté client, car cela peut être facilement contourné. Pour éviter que des valeurs incorrectes ou malveillantes soient enregistrées ou entraînent une exécution incorrecte de la logique de l'application, il est important de valider également les valeurs d'entrée côté serveur.

Spring MVC prend en charge la validation côté serveur en utilisant les annotations de spécificationJSR 349 Bean Validation. Pour cet exemple, nous utiliserons l'implémentation de référence de la spécification, qui esthibernate-validator.

3.1. Le modèle de données

Créons une classeUser dont les propriétés sont annotées avec les annotations de validation appropriées:

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
}

Les annotations utilisées ci-dessus appartiennent à la spécificationJSR 349, à l'exception de@Email et@NotBlank, qui sont spécifiques à la bibliothèquehibernate-validator.

3.2. Contrôleur Spring MVC

Créons une classe de contrôleur qui définit un point de terminaison/user, qui sera utilisé pour enregistrer un nouvel objetUser dans unList.

Afin de permettre la validation de l'objetUser reçu via les paramètres de requête, la déclaration doit être précédée de l'annotation@Valid, et les erreurs de validation seront conservées dans une instanceBindingResult.

Pour déterminer si l'objet contient des valeurs non valides, nous pouvons utiliser la méthodehasErrors() deBindingResult.

SihasErrors() renvoietrue, on peut renvoyer unJSON array contenant les messages d'erreur associés aux validations qui n'ont pas réussi. Sinon, nous allons ajouter l'objet à la liste:

@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);
        }
    }
}


Comme vous pouvez le voir,server-side validation adds the advantage of having the ability to perform additional checks that are not possible on the client side.

Dans notre cas, nous pouvons vérifier si un utilisateur avec la même adresse e-mail existe déjà - et renvoyer un statut de 409 CONFLICT si tel est le cas.

Nous devons également définir notre liste d'utilisateurs et l'initialiser avec quelques valeurs:

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));

Ajoutons également un mappage pour récupérer la liste des utilisateurs en tant qu'objet JSON:

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

Le dernier élément dont nous avons besoin dans notre contrôleur Spring MVC est un mappage pour renvoyer la page principale de notre application:

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

Nous allons jeter un oeil à la pageuser.html plus en détail dans la section AngularJS.

3.3. Configuration Spring MVC

Ajoutons une configuration MVC de base à notre application:

@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. Initialisation de l'application

Créons une classe qui implémente l'interfaceWebApplicationInitializer pour exécuter notre application:

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. Test de la validation Spring Mvc à l'aide de Curl

Avant d'implémenter la section client AngularJS, nous pouvons tester notre API à l'aide de cURL avec la commande:

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

La réponse est un tableau contenant les messages d'erreur par défaut:

[
    "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. Validation AngularJS

La validation côté client est utile pour créer une meilleure expérience utilisateur, car elle fournit à l'utilisateur des informations sur la manière de soumettre avec succès des données valides et lui permet de continuer à interagir avec l'application.

La bibliothèque AngularJS prend en charge l’ajout d’exigences de validation aux champs de formulaire, la gestion des messages d’erreur et le style des formulaires valides et non valides.

Commençons par créer un module AngularJS qui injecte le modulengMessages, qui est utilisé pour les messages de validation:

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

Ensuite, créons un service et un contrôleur AngularJS qui utiliseront l'API intégrée dans la section précédente.

4.1. Le service AngularJS

Notre service utilisera deux méthodes appelant les méthodes du contrôleur MVC: une pour enregistrer un utilisateur et une pour récupérer la liste des utilisateurs:

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. Le contrôleur AngularJS

Le contrôleurUserCtrl injecte lesUserService, appelle les méthodes de service et gère la réponse et les messages d'erreur:

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();
}]);

Nous pouvons voir dans l'exemple ci-dessus que la méthode de service n'est appelée que si la propriété$valid deuserForm est vraie. Pourtant, dans ce cas, il y a la vérification supplémentaire des e-mails en double, qui ne peut être effectuée que sur le serveur et est traitée séparément dans la fonctionerror().

Notez également qu'il existe une variablesubmitted définie qui nous dira si le formulaire a été soumis ou non.

Initialement, cette variable serafalse, et lors de l'invocation de la méthodesaveUser(), elle devienttrue. Si nous ne voulons pas que les messages de validation s'affichent avant que l'utilisateur soumette le formulaire, nous pouvons utiliser la variablesubmitted pour éviter cela.

4.3. Formulaire utilisant la validation AngularJS

Afin de pouvoir utiliser la bibliothèque AngularJS et notre module AngularJS, nous devrons ajouter les scripts à notre pageuser.html:



Ensuite, nous pouvons utiliser notre module et notre contrôleur en définissant les propriétésng-app etng-controller:

Créons notre formulaire HTML:

...

Notez que nous devons définir l'attributnovalidate sur le formulaire afin d'éviter la validation HTML5 par défaut et le remplacer par le nôtre.

L'attributng-class ajoute dynamiquement la classe CSSform-error au formulaire si la variablesubmitted a une valeur detrue.

L'attributng-submit définit la fonction de contrôleur AngularJS qui sera appelée lorsque le formulaire sera soumis. L'utilisation deng-submit au lieu deng-click présente l'avantage de répondre également à l'envoi du formulaire à l'aide de la touche ENTRÉE.

Ajoutons maintenant les quatre champs de saisie pour les attributs utilisateur:











Chaque champ d'entrée a une liaison à une propriété de la variableuser via l'attributng-model.

For setting validation rules, nous utilisons l'attribut HTML5required et plusieurs attributs spécifiques à AngularJS:ng-minglength, ng-maxlength, ng-min, etng-trim.

Pour le champemail, nous utilisons également l'attributtype avec une valeur deemail pour la validation des e-mails côté client.

In order to add error messages corresponding to each field, AngularJS propose la directiveng-messages, qui parcourt l'objet$errors d'une entrée et affiche les messages en fonction de chaque règle de validation.

Ajoutons la directive pour le champemail juste après la définition d'entrée:

Invalid email!

Email is required!

Des messages d'erreur similaires peuvent être ajoutés pour les autres champs de saisie.

We can control when the directive is displayed pour le champemail en utilisant la propriéténg-show avec une expression booléenne. Dans notre exemple, nous affichons la directive lorsque le champ a une valeur non valide, ce qui signifie que la propriété$invalid esttrue et que la variablesubmitted est égalementtrue.

Un seul message d'erreur sera affiché à la fois pour un champ.

Nous pouvons également ajouter une coche (représentée par le caractère de code HEX ✓) après le champ de saisie au cas où le champ serait valide, en fonction de la propriété$valid:

La validation AngularJS offre également un support pour le style en utilisant des classes CSS telles queng-valid etng-invalid ou plus spécifiques commeng-invalid-required etng-invalid-minlength.

Ajoutons la propriété CSSborder-color:red pour les entrées non valides dans la classeform-error du formulaire:

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

Nous pouvons également afficher les messages d'erreur en rouge à l'aide d'une classe CSS:

.error-messages {
    color:red;
}

Après avoir tout rassemblé, voyons un exemple de ce à quoi ressemblera notre validation de formulaire côté client une fois rempli avec un mélange de valeurs valides et non valides:

AngularJS form validation example

5. Conclusion

Dans ce didacticiel, nous avons montré comment nous pouvons combiner la validation côté client et côté serveur à l'aide d'AngularJS et de Spring MVC.

Comme toujours, le code source complet des exemples peut être trouvéover on GitHub.

Pour afficher l'application, accédez à l'URL de/userPage après l'avoir exécutée.