AngularJSでinput formのエラー表示 - inokawablog

AngularJSでinput formのエラー表示

本当に苦労しました。いつもはvue.jsをひたすら触っているので、angularjsを理解するのに苦労しました。

リアルタイムのエラー表示を作るのは簡単ですが、どこでも使い回せるように作りました。

・ リアルタイムにエラーが表示される
・ 最初の画面ロード時はエラーが表示されてないで、入力し始めてエラーが表示される。
・ input clearableができる
・ なるべく他のところにも共通で使用できる

なぜangularjsかというと、仕事で使ったからです笑。AngularJSは癖があって難しかったですが、なんとか要件を満たすようにできました。

まずコードを説明し、いくつか要点をまとめておきます。

form.html

<!doctype html>
<html ng-app="form" >
<head>
  <meta charset="utf-8">
</head>
    <body ng-controller="FormCtrl">
      <form name="myForm">
        <input-form type="{{formData.type}}" rules="formData.rules" label="{{formData.label}}" param="formData.param" trim="{{formData.trim}}" placeholder="{{formData.placeholder}}" show-error="formData.showError" clearable="formData.clearable"></input-form>
      </form>
    </body>
</html>

template.html

ディレクティブで読み込むhtml

<div class="form-group">
    <label class="col-sm-3 control-label">{{label}}</label>
    <div class="col-sm-9">
        <input placeholder="{{placeholder}}" ng-model="param" ng-required="rules.required.status" ng-maxlength="rules.maxlength.max" ng-minlength="rules.minlength.min" ng-trim="trim">
        <span ng-show="clearable && param" ng-click="clearText()" form-control-feedback" uib-tooltip="clear">x</span>
        <div class="error-messages" ng-show="showError && error && dirty">
            <div ng-show="errors.required">{{rules.required.message}}</div>
            <div ng-show="errors.pattern">{{rules.regex.message}}</div>
            <div ng-show="errors.maxlength">{{rules.maxlength.message}}</div>
        </div>
    </div>
</div>
var app = angular.module('form', []);

/* Controllers */
// patternとmaxlengthを併用する場合、patternが優先される
// https://stackoverflow.com/questions/18516001/angularjs-ng-minlength-validation-is-not-working-form-still-being-submitted
function FormCtrl($scope) {
  $scope.formData = {
    label: "label1",
    placeholder: 'placeholderだよ',
    param: "aaa",
    trim: false,
    clearable: true,
    type: "text",
    showError: true,
    rules: {
      required: {
        status: true,
        message: 'required'
      },
      minlength: { 
        min: 2,
        message: 'min'
      },
      maxlength: {
        max: 10,
        message: 'max'
      },
      regex: {
        message: 'pattern',
        pattern: /^[0-9]{3}-[0-9]{4}$/
      }
    },
  };
};

// 入力値がvalidationに引っかかっている場合、paramはundefinedになる
// https://www.buildinsider.net/web/angularjstips/0060
// placeholder="{{placeholder}}" ng-attr-placeholder="{{placeholder}}"
// そのためpatternの処理のみ別で行う
app.directive('inputForm', function() {
  var scope, linkFn, template, success, checkError, clearText;
  template = 'path to template.html';

  scope = {
    label: '@',
    placeholder: '@',
    trim: '@',
    clearable: '=',
    showError: '=',
    rules: '=',
    param: '='
  };

  linkFn = function(scope, el, attrs, formCtrl) {
    var regexResult;
    scope.clearText = function() {
      formCtrl.$setDirty(false);
      scope.param = '';
    };
    if ( formCtrl.$name === undefined ) throw new Error('please set form unique name');
    scope.errors = formCtrl.$error;
    scope.$watch('param', function(newValue, oldValue) {
      scope.errors.pattern = false;
      scope.error = formCtrl.$invalid;
      regexResult = scope.rules.regex.pattern.test(newValue);
      if ( newValue && !regexResult ) {
         scope.error = !regexResult;
         scope.errors.pattern = !regexResult;
      }
      scope.dirty = formCtrl.$dirty
    });
  };

  return {
    restrict: 'E',
    scope: scope,
    template: template,
    require: '^form',
    link: linkFn
  };
});

ポイント

restrict

restrictのパラメーターは以下のようになっています。

option Description
E タグ
A 属性
C クラス
M コメント

作成したディレクティブの使い方によって選択して下さい。

今回はinputForm<input-form></input-form>のようにタグとして使用したのでrestrictEを指定しています。

<div input-form="something"></div>のように属性として使用した場合は、restrictAにします。

<span class="my-dir: exp;"></span>のようにクラスとして使用する場合は、Cにして下さい。

そしてMはあまり使わないと思いますが、<!-- directive: my-dir exp -->のように使用します。

scope

scopeのパラメーターは、以下のようになっています。

Property Description
@ 親から子への単方向バインド
= 双方向バインド
& 関数

このようになっており、値の渡し方が異なります。

@は、{{rules.formData.label}}のように{{}}をつけて親から子へ値を渡しますが、=の双方向バインドでは、clearable="formData.clearable"のように。、{{}}をつけて渡す必要はありません。

最初はこれにちょっとだけハマりました。

template

templateは別のファイルに分けて読み込んでください。

link

formCtrlで使用できるパラメータは以下になっています。

Property Description
$dirty form内のinputを一度でも変更した
$pristine form内のinputが全て変更されていない
$valid form内のinputが全てvalid
$invalid form内のinputにinvalidがある
$submitted formがsubmitされた

このようになっており、今回は$dirtyと$validを使用しています。

初期状態ではエラーを表示させないので$dirtyをscopeに保持して、pattern以外のエラー判定は$validを使っています。

validation

ここで一つ注意しておきたいのが、patternです。デフォルトの値を設定したい場合、patternの正規表現にマッチしていないとデフォルトでundefinedになってしまいます。そのためデフォルトの値が設定できません!!

それを回避するために、

      regexResult = scope.rules.regex.pattern.test(newValue);
      if ( newValue && !regexResult ) {
         scope.error = !regexResult;
         scope.errors.pattern = !regexResult;
      }

このようにして、patternだけ個別でエラー判定しています。

他の記事ではng-model-options="{ allowInvalid: true }"という設定をしていたのですが、私の環境では動きませんでしたし、ブラウザによって動かないことが多くあるそうです。なので、少々面倒ですが、自分で設定する以外に方法はないと思います。

まとめ

AngularJSは少し使いずらいですね。一応CodePenで作ったものをこちらに置いておきます。

AngularJSを使っている人は少ないと思いますが、誰かの参考になれば幸いです、それでは〜。