Help us understand the problem. What is going on with this article?

AngularJSのForm Validationでカスタムバリデーションのやり方あれこれ(個人的にはui-validateが好き)

More than 1 year has passed since last update.

最近はNexus7を使ってデュアルディスプレイ化している大橋です。

AngularJSでrequiredng-maxlengthを使った定義済みのForm Validationはよく見るのですが、
そんなものだけじゃはっきり言って足りないと思う今日このごろ。

そこでカスタムバリデーションを組み始めたのですが
普通にやると色々面倒だったり、とりあえずui-validate使ってればいいんじゃないという感じがしたので
まとめたいと思います。

結論的な

色んな所で使うカスタムバリデーションならDirectiveを作る
一箇所でしか使わないならui-validateを使うって感じがよいかなーと
あと細かくイベント制御できるのはDirectiveの方です。
値が変わるごと=フォームに入力があるごとにバリデーションが動いていいならui-validateという感じかと。

今回のお題

よくあるパスワード確認フォームとか、メールアドレス確認フォームとかの、
2つのテキストボックスで同じ値が入っているか確認するやーつを実装したいと思います。

見た目は以下です。

なお今回作ったものはPlunkerにおいてあります。
http://plnkr.co/edit/eIhqY4

やり方

やり方1 Directive作る(公式に載ってるやり方)

公式に載ってるカスタムバリデーションでの実装方法です。

やり方としては

  1. ngModelをrequiredした独自Directive作る
  2. linkプロセスでngModelControllerの$parsersに独自バリデーション処理を追加
  3. ngModelController.$setValidityで結果を設定する

という感じ

作るもの

sameという名のdirectiveを作り属性値として、参照先のスコープのmodel名を渡します。
その中でカスタムバリデーションを行います。

コード

html

input01,input02というテキストフィードを作って、input02の方に作成するsame directiveを設定しています。

index.html
    <div ng-controller="AppCtrl as ctrl" class="container-fluid">
      <div class="row-fluid">
        <div class="span12">
          <form name="hogeForm" class="form-horizontal">
            <fieldset>
              <legend>Controls Bootstrap supports</legend>
              <div class="control-group">
                <label for="input01" class="control-label">Email1</label>
                <div class="controls">
                  <input id="input01" type="text" name="input01" ng-model="ctrl.val.input01" class="input-xlarge">
                </div>
              </div>
              <div class="control-group">
                <label for="input02" class="control-label">Email2</label>
                <div class="controls">
                  <input id="input02" type="text" name="input02" ng-model="ctrl.val.input02" same="ctrl.val.input01" class="input-xlarge">
                </div>
              </div>{{ctrl.val}} {{hogeForm.input02.$error | json}}
            </fieldset>
          </form>
        </div>
      </div>
    </div>
    <script type="text/coffeescript" src="app.coffee"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
    <script src="//angular-ui.github.io/ui-utils/dist/ui-utils.js"></script>

js (directive)

directiveではngModelを必須(#1)として、
linkにてNgModelController(#2)を受け取るようにし、
NgModelController$parsersに処理を追加、引数は現在入力されている値になります。(#3)
属性値で受け取った比べる対象のmodel値を取得(#4)し、
NgModelController$setValidityにてエラーの設定を行います。
$setValidityの引数は第一引数がエラーの名称で、第二引数が結果(正常か否か)です。

また戻り値(#6)がngModelとしての値になります。
エラーの場合undefinedで返却したり、
このタイミングで値の変換を行います。(多分もともと$parsersはそのためのもの?)

app.coffee
angular.module "app", []
  .controller 'AppCtrl' , 
    [
      '$scope',
      class AppCtrl
        constructor : (@$scope)->
    ]
  .directive 'same', ()->
    require : 'ngModel' #1
    link : (scope, elem, attrs, modelCtrl)-> #2

      modelCtrl.$parsers.push (viewValue)-> #3

        return viewValue if !attrs.same?
        val = scope.$eval(attrs.same) #4
        return viewValue if !val?


        if val != viewValue 
          modelCtrl.$setValidity "same" , false #5
          return undefined #6
        else
          modelCtrl.$setValidity "same" , true #5
          return viewValue #6

このやり方の場合、今回のようなどこでも使いそうなdirectiveの場合は特に問題ないのですが、
Controllerの状態に強く紐づくものや
一箇所でしか使わないバリデーションの場合はちょっと面倒です。

やり方2 ui-validate使う

ui-validateはui-bootstrapなどを作ってるAngularUIの中の一つui-utilsに含まれているカスタムバリデーションを定義するためのdirectiveです。
なおui-utils全部入れなくても単体でも使えます。

先ほど上げた、自前でdirectiveを作るのに比べて

  • わざわざディレクティブ作らなくて良い
  • Controllerのデータが扱いやすい

などの利点があります。

作るもの

特に無いです。
ui-validateがどうにかしてくれます。
場合によってcontrollerにメソッドを追加します。

コード

html

ui-validateを利用してsameというエラー名で$value(ここではinput02の値)とctrl.val.input03の値を比べています。
これぐらいの比較であればControllerにメソッドを設定する必要もありません。
細かい制御が必要な場合はControllerにメソッドを記載し、
それを呼び出すだけでよいです。

index.html
<div ng-controller="AppCtrl as ctrl" class="container-fluid">
      <div class="row-fluid">
        <div class="span12">
          <form name="hogeForm" class="form-horizontal">
            <fieldset>
              <legend>Controls Bootstrap supports</legend>
              <div class="control-group">
                <label for="input01" class="control-label">Email1</label>
                <div class="controls">
                  <input id="input01" type="text" name="input01" ng-model="ctrl.val.input01" class="input-xlarge">
                </div>
              </div>
              <div class="control-group">
                <label for="input02" class="control-label">Email2</label>
                <div class="controls">
                  <input id="input02" type="text" name="input02" ng-model="ctrl.val.input02" ui-validate="{same : '$value==ctrl.val.input01'}" class="input-xlarge">
                </div>
              </div>{{ctrl.val}} {{hogeForm.input02.$error | json}}
            </fieldset>
          </form>
        </div>
      </div>
    </div>
    <script type="text/coffeescript" src="app.coffee"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
    <script src="//angular-ui.github.io/ui-utils/dist/ui-utils.js"></script>

js(controller)

Controllerでは一応isSameというメソッドを用意しています。
これを呼び出す場合は上記の
ui-validate="{same : '$value==ctrl.val.input01'}"
ui-validate="{same : 'isSame($value,ctrl.val.input01)'}"に変更するだけです。

app.coffee
angular.module "app", ['ui.utils']
  .controller 'AppCtrl' , 
    [
      '$scope',
      class AppCtrl
        constructor : (@$scope)->
        isSame : ($value, value2)-> 
          return $value == value2
    ]

こちらのほうが個人的にはわかりやすいのとController側のデータが扱いやすいように思います。

ただElementに深く関わるようなことをしたり、
イベントに紐付かせてやる場合はDirectiveのほうが向いているのかなーと思います。

まとめ

とりあえずAngularJSのカスタムバリデーションは結構素敵です。
処理とUIを分離できるのはコード的には書きやすいです。

soundTricker
Google API、GSuite、GCP、Angular(1&2)、Google Apps Scriptらへんの人 一応Google Developer Expert(Apps Script)です。 https://developers.google.com/community/experts/directory/profile/profile-keisuke_oohashi
https://plus.google.com/u/0/112329532641745322160/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした