入力項目が大量にあるようなフォームをangularJSで作る場合に、ラベル・テキストinput・validationエラー表示などをひとまとめにしたdirectiveを作ると、UIが変わったりしても全体に一気に反映できるので便利です。
<div ng-form name='testForm'>
<div my-input id="name" ng-model="formData.name" ng-required="true" placeholder="山田太郎" title="氏名"></div>
<div my-input id="zip-code" ng-model="formData.zipCode" ng-required="true" placeholder="1234567" title="郵便番号" validation="patterns.zipCode"></div>
<div my-input id="phone-number" ng-model="formData.phoneNumber" ng-required="true" placeholder="0312345678" title="電話番号" validation="patterns.phone"></div>
<button class="button btn btn-primary btn-block" ng-disabled="testForm.$invalid">送信</button>
</div>
angular.module('TestApp', [])
.controller('myCtrl', ['$scope', function ($scope) {
//validationまわりをエラー文言と一緒にまとめて定義しています
$scope.patterns = {
zipCode: {
regexp: '/^[0-9]{7}$/',
msg: 'ハイフンなしの7桁の数字で入力してください',
maxlength: 7
},
phone: {
regexp: '/^[0-9]{10,11}$/',
msg: 'ハイフンなしの半角数字を10〜11桁で入力してください',
maxlength: 11
}
};
}])
.directive('myInput', function () {
return {
restrict: 'A',
require: ['^?form'],
transclude: true,
replace: true,
scope: {
title: '@',
id: '@',
ngModel: '=',
ngRequired: '=',
validation: '=',
placeholder: '@?',
errMsg: '=?'
},
controller: ['$scope', function ($scope) {
// create error msg
if (!$scope.errMsg) {
$scope.errMsg = {};
}
if ($scope.ngRequired === true && !('required' in $scope.errMsg)) {
$scope.errMsg.required = $scope.title + 'を入力して下さい';
}
if ($scope.validation && !('invalid' in $scope.errMsg)) {
$scope.errMsg.invalid = $scope.validation.msg;
}
}],
link: function (scope, element, attrs, ctrl) {
scope.target = ctrl[0][attrs.id];
},
templateUrl: 'my-input.html'
};
});
<div class="field" class="form-group" ng-class="{'has-error': target.$dirty && target.$invalid}">
<label ng-bind="title" class="control-label"></label><input name="{{id}}" ng-model="ngModel" ng-required="ngRequired" ng-pattern="{{validation.regexp}}" maxlength="{{validation.maxlength}}" placeholder="{{placeholder}}" type="text" class="form-control" />
<div class="errors" ng-show="target.$dirty">
<p ng-bind="errMsg.required" ng-if="target.$error.required && errMsg.required"></p>
<p ng-bind="errMsg.invalid" ng-if="!target.$error.required && target.$invalid && errMsg.invalid"></p>
</div>
</div>
こんな感じになります。
※注意点1
id
というattributeをinputのnameに引き継いでいますが、このattribute名をinputに合わせてname
にしてしまうと、ngModelの$errorの内容がうまくupdateされません。
※注意点2
動的に生成したinputのNgModelControllerが、FormController内にinputのname
のvalueをキーにして保持されるのを想定したのですが、template内に記述した{{id}}
をキーとしてしまっているため、nameごとに参照できなくなっています。
この問題は
https://github.com/angular/angular.js/issues/1404
にissueとして上がっています。
この回避策
https://github.com/angular/angular.js/issues/1404#issuecomment-30859987
によって期待通りの動きになります。
なお、どちらの問題も、angularを1.3.5にアップデートしたところ、修正されているようでした。
以下は1.2系と1.3系での動作サンプルになります。
angular 1.2.16
http://jsfiddle.net/miyukiw/m03f2ymt/4/
angular 1.3.5
http://jsfiddle.net/miyukiw/9d64oa1m/5/
(※1.2と1.3ではng-patternのvalueがstringから正規表現オブジェクトに変わっているところにも注意)