AngularJSとSpring MVCによるフォーム検証

AngularJSとSpring MVCによるフォーム検証

1. 概要

検証は、予想したほど簡単ではありません。 そしてもちろん、ユーザーがアプリケーションに入力した値を検証することは、データの整合性を維持するために非常に重要です。

Webアプリケーションのコンテキストでは、データ入力は通常HTMLフォームを使用して行われ、クライアント側とサーバー側の両方の検証が必要です。

このチュートリアルでは、implementing client-side validation of form input using AngularJS and server-side validation using the Spring MVC frameworkについて説明します。

2. Mavenの依存関係

まず、次の依存関係を追加しましょう。


    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

spring-webmvchibernate-validator、およびjackson-databindの最新バージョンは、MavenCentralからダウンロードできます。

3. Spring MVCを使用した検証

これは簡単に回避できるため、アプリケーションはクライアント側の検証のみに依存しないでください。 誤った値や悪意のある値が保存されたり、アプリケーションロジックが不適切に実行されたりするのを防ぐには、サーバー側でも入力値を検証することが重要です。

Spring MVCは、JSR 349 Bean Validation仕様アノテーションを使用してサーバー側の検証をサポートします。 この例では、仕様のリファレンス実装であるhibernate-validatorを使用します。

3.1. データモデル

適切な検証アノテーションが付けられたプロパティを持つUserクラスを作成しましょう。

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
}

上記で使用されている注釈は、hibernate-validatorライブラリに固有の@Email@NotBlankを除いて、JSR 349仕様に属しています。

3.2. Spring MVCコントローラー

/userエンドポイントを定義するコントローラークラスを作成しましょう。これは、新しいUserオブジェクトをListに保存するために使用されます。

リクエストパラメータを介して受信したUserオブジェクトの検証を有効にするには、宣言の前に@Validアノテーションを付ける必要があり、検証エラーはBindingResultインスタンスで保持されます。

オブジェクトに無効な値が含まれているかどうかを判断するには、BindingResulthasErrors()メソッドを使用できます。

hasErrors()trueを返す場合、合格しなかった検証に関連するエラーメッセージを含むJSON arrayを返すことができます。 それ以外の場合は、オブジェクトをリストに追加します。

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


ご覧のとおり、server-side validation adds the advantage of having the ability to perform additional checks that are not possible on the client side.

この場合、同じメールアドレスのユーザーがすでに存在するかどうかを確認できます。存在する場合は、ステータス409CONFLICTを返します。

また、ユーザーのリストを定義し、いくつかの値で初期化する必要があります。

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

ユーザーのリストをJSONオブジェクトとして取得するためのマッピングも追加しましょう。

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

Spring MVCコントローラーで必要な最後の項目は、アプリケーションのメインページを返すマッピングです。

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

AngularJSセクションでuser.htmlページを詳しく見ていきます。

3.3. Spring MVC設定

基本的なMVC構成をアプリケーションに追加しましょう。

@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. アプリケーションの初期化

アプリケーションを実行するためにWebApplicationInitializerインターフェースを実装するクラスを作成しましょう。

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. Curlを使用したSpringMvc検証のテスト

AngularJSクライアントセクションを実装する前に、次のコマンドでcURLを使用してAPIをテストできます。

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

応答は、デフォルトのエラーメッセージを含む配列です。

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

クライアント側の検証は、有効なデータを正常に送信する方法に関する情報をユーザーに提供し、ユーザーがアプリケーションと引き続き対話できるようにするため、ユーザーエクスペリエンスの向上に役立ちます。

AngularJSライブラリは、フォームフィールドに検証要件を追加し、エラーメッセージを処理し、有効なフォームと無効なフォームをスタイル設定するための優れたサポートを提供します。

まず、検証メッセージに使用されるngMessagesモジュールを挿入するAngularJSモジュールを作成しましょう。

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

次に、前のセクションで構築したAPIを使用するAngularJSサービスとコントローラーを作成しましょう。

4.1. AngularJSサービス

このサービスには、MVCコントローラーメソッドを呼び出す2つのメソッドがあります。1つはユーザーを保存するため、もう1つはユーザーのリストを取得するためです。

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. AngularJSコントローラー

UserCtrlコントローラーはUserServiceを挿入し、サービスメソッドを呼び出し、応答メッセージとエラーメッセージを処理します。

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

上記の例では、userForm$validプロパティがtrueの場合にのみサービスメソッドが呼び出されることがわかります。 それでも、この場合、重複する電子メールの追加チェックがあります。これはサーバー上でのみ実行でき、error()関数で個別に処理されます。

また、フォームが送信されたかどうかを示すsubmitted変数が定義されていることに注意してください。

最初、この変数はfalseになり、saveUser()メソッドを呼び出すと、trueになります。 ユーザーがフォームを送信する前に検証メッセージを表示したくない場合は、submitted変数を使用してこれを防ぐことができます。

4.3. AngularJS検証を使用したフォーム

AngularJSライブラリとAngularJSモジュールを利用するには、user.htmlページにスクリプトを追加する必要があります。



次に、ng-appおよびng-controllerプロパティを設定することにより、モジュールとコントローラーを使用できます。

HTMLフォームを作成しましょう:

...

デフォルトのHTML5検証を防ぎ、独自の検証に置き換えるために、フォームにnovalidate属性を設定する必要があることに注意してください。

submitted変数の値がtrueの場合、ng-class属性はform-errorCSSクラスをフォームに動的に追加します。

ng-submit属性は、フォームが送信されたときに呼び出されるAngularJSコントローラー関数を定義します。 ng-clickの代わりにng-submitを使用すると、ENTERキーを使用したフォームの送信にも応答するという利点があります。

次に、ユーザー属性の4つの入力フィールドを追加しましょう。











各入力フィールドには、ng-model属性を介してuser変数のプロパティへのバインディングがあります。

For setting validation rulesでは、HTML5required属性といくつかのAngularJS固有の属性ng-minglength, ng-maxlength, ng-min,およびng-trimを使用します。

emailフィールドには、クライアント側の電子メール検証に値emailtype属性も使用します。

In order to add error messages corresponding to each field、AngularJSはng-messagesディレクティブを提供します。これは、入力の$errorsオブジェクトをループし、各検証ルールに基づいてメッセージを表示します。

入力定義の直後にemailフィールドのディレクティブを追加しましょう。

Invalid email!

Email is required!

他の入力フィールドにも同様のエラーメッセージを追加できます。

ブール式でng-showプロパティを使用するemailフィールドのWe can control when the directive is displayed。 この例では、フィールドの値が無効な場合、つまり$invalidプロパティがtrueであり、submitted変数もtrueである場合に、ディレクティブを表示します。

フィールドに対して一度に表示されるエラーメッセージは1つだけです。

$validプロパティに応じて、フィールドが有効な場合は、入力フィールドの後にチェックマーク記号(HEXコード文字✓で表される)を追加することもできます。

AngularJS検証では、ng-validng-invalidなどのCSSクラス、またはng-invalid-requiredng-invalid-minlengthなどのより具体的なクラスを使用したスタイリングのサポートも提供されます。

フォームのform-errorクラス内の無効な入力に対してCSSプロパティborder-color:redを追加しましょう。

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

CSSクラスを使用して、エラーメッセージを赤で表示することもできます。

.error-messages {
    color:red;
}

すべてをまとめた後、有効な値と無効な値を組み合わせて入力した場合に、クライアント側のフォーム検証がどのように表示されるかの例を見てみましょう。

AngularJS form validation example

5. 結論

このチュートリアルでは、AngularJSとSpringMVCを使用してクライアント側とサーバー側の検証を組み合わせる方法を示しました。

いつものように、例の完全なソースコードはover on GitHubにあります。

アプリケーションを表示するには、実行後に/userPageのURLにアクセスします。