[AngularJS] ngMessages 対応カスタムバリデータの作り方

2015.11.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

angularjs125title

車輪開発大好きおたいがです。こんにちは。( 挨拶 )

今回は AngularJS 1.3 で実装された ngMessages に対応したカスタムバリデータの作り方をまとめました。

通常の使用方法

通常 ngMessages を使用したバリデーションを使用するときには、入力系 DOM 要素 ( input, textarea, select ) は以下の条件を満たす必要があります。

  • name 属性を定義している
  • ngModel を定義している
  • 1 つ以上の専用ディレクティブ ( ng-required, ng-minlength, ng-pattern など ) を定義している
  • name 属性を定義している <form> 要素、または、ngForm を定義したブロック要素を親 ( 先祖 ) としている

リファレンスに記述されている実装例

<form name="sampleForm">

  <label>Enter your name:</label>

  <input type="text"
         name="myName"
         ng-model="name"
         ng-minlength="5"
         ng-maxlength="20"
         ng-required="true" />

  <pre>sampleForm.myName.$error = {{ sampleForm.myName.$error | json }}</pre>

  <div ng-messages="sampleForm.myName.$error">
    <div ng-message="required">You did not enter a field</div>
    <div ng-message="minlength">Your field is too short</div>
    <div ng-message="maxlength">Your field is too long</div>
  </div>
  
</form>

上記コードは「入力必須」「 5 文字以上」「 20 文字以下」の条件を満たせなかったときに、定義されたメッセージが表示されるよう実装されています。

お手製カスタムバリデータのサンプル紹介

今回紹介するのは「最低 1 つ以上のチェックボックスを選択することを必須とする」バリデータのディレクティブ ( cmCheckBoxesRequired ) です。複数のチェックボックスを保持する親要素に対して定義することを前提としています。

コントローラで配列やオブジェクトを走査操作して、各チェックボックスのモデルを確認して…といったロジックをサービスか何かの関数に用意しても良いとは思うのですが、他のバリデーション専用ディレクティブと実装を合わせた方がスマートと個人的には思うので少し頑張りました。

サンプル

解説の前にお手製サンプルをご確認ください。

[CHECK] ボタンを押下するとバリデータが有効になり、ngModel の値を監視し続けます。( [RESET] ボタン押下でバリデータの監視状態が初期状態に戻ります )

上記サンプルを元に解説を進めます。

name 属性と ngModel が必須

name 属性は ngForm で参照するときに必要なので特に気にすることは無いのですが、UI ではない…ただの親コンテナ役である DOM 要素に ngModel を定義する必要があります。

単純に適当なオブジェクトを突っ込めば良いわけではなく、各子要素チェックボックスが保持する ngModel の親オブジェクトを持たせます。DOM 要素の親子関係構造と同じように ngModel の親子関係の構造も保たせることが必要となります。

…
ctrl.checkboxes = [
  {'value' : false, 'label' : '選択肢 1' },
  {'value' : false, 'label' : '選択肢 2' },
  {'value' : false, 'label' : '選択肢 3' },
  {'value' : false, 'label' : '選択肢 4' }
];
<section name="checkboxes" ng-model="ctrl.checkboxes" cm-check-boxes-required="">
  …
  <div class="checkbox" ng-repeat="item in ctrl.checkboxes">
    <label><input type="checkbox" ng-model="item.value" />{{item.label}}</label>
  </div>
</section>

バリデータ ディレクティブと一緒に定義した ngModel の使い道

定義された ngModel に付随する NgModelController を使用することで ngMessage の定義値 ( 任意のバリデータ名 )と同期させることができます。これらが ngModel を必要とする主な理由です。( バリデーションのロジック次第ですが、必ずしも ngModel のデータソースが必要というわけではないということです )

<form class="container" name="sampleForm">
  <section name="checkboxes" ng-model="ctrl.checkboxes" cm-check-boxes-required="">

    <div ng-messages="sampleForm.checkboxes.$error" ng-if="sampleForm.$submitted">
      <p ng-message="checkBoxesRequired">1 つ以上選択してください</p>
    </div>

    <div class="checkbox" ng-repeat="item in ctrl.checkboxes">
      <label><input type="checkbox" ng-model="item.value" />{{item.label}}</label>
    </div>

  </section>
</form>

上記コード内にあるバリデータ名 checkBoxesRequired はディレクティブ内で以下のように関連付けされます。

…
.directive('checkBoxesRequired', function() {
  return {
   'restrict' : 'A',
   'require'  : '?ngModel',
   'link'     : function(scope, elm, attr, ctrl) {
      var invalid = false;
      var jqContainer = angular.element(elm);
      
      var checkVlidatate = function() {
        //チェックされているチェックボックスの数を算出 ( 要・jQuery )
        var result = jqContainer.find('input[type=\'checkbox\']:checked');
        return !angular.equals(result.length, 0);
      };

      //スコープ経由で ngModel 値を監視
      var watcher = scope.$watch(attr.ngModel, function(newValue, oldValue) {
        if(!newValue || !oldValue) {
          return;
        } else if (!angular.equals(newValue, oldValue)) {
          invalid = checkVlidatate();
          //バリデータ名 'checkBoxesRequired' の結果を NgModelController にセット
          ctrl.$setValidity('checkBoxesRequired', invalid);
        }
      }, true);
      
      //NgModelController のバリデータのコレクション (.$validators) に
      //バリデータ名と同名の検証関数を定義
      ctrl.$validators.checkBoxesRequired = function(modelValue, viewValue) {
        return invalid;
      };
      
      var destroy = scope.$on('$destroy', function() {
        watcher();
        destroy();
      });
    }
 };
})

さいごに

以上が ngMessages に対応したカスタムバリデータの作り方でした。NgModelController の機能を知ることがお行儀のよい実装への第一歩だと思いました。リファレンスが少々不親切気味で実装は多少難しいかもしれませんが、1 回作ってしまえば、簡単に再利用できるので頑張る甲斐はあるのではないかと思います。