AngularJS 1.3のフォームまわりの機能強化
- カテゴリ :
- フロントエンド(HTML5)
- タグ :
- Tech
- JavaScript
- angularjs
こんにちは中川です。
先日、AngularJS 1.3 がリリースされましたね。
動作速度の改善や、メモリ消費量の削減などパフォーマンス面での改善もうれしいところですが、
機能的にはフォーム関連の機能強化が特にうれしく感じましたので、紹介したいと思います。
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
ngModel.$validators を使うと、独自のバリデーション関数を簡単に定義することができるようになりました。
以下の例のように、入力値を引数で受け取り、返り値で真偽値を返す関数を$validatorsオブジェクトに定義します。
$validatorsのキー(ここではvalidCharacters)が、エラーメッセージ表示時などの参照用に利用できます。
サーバへの問い合わせが必要な場合など、非同期の処理がある場合のバリデーションにも対応しています。
以下の例のように、ngModel.$asyncValidators にpromiseを返す関数を定義します。
今まで、パスワード確認入力やユーザー名重複のサーバ問い合わせなど、
controller(service)の値を組み合わせたバリデーションをdirectiveで
指定するのが面倒で、ui-validateというcontrollerの関数定義を指定できる外部モジュールを利用していました。
ui-vaidateでも今まで対応できていたので、別にそれでもいいといえばいいのですが、
しかし、1.3からは以下のような汎用的なdirectiveを定義すれば、とても素直に指定することができます。
利用時は以下のように、controllerでオブジェクトをテンプレート側のapp-validators属性で渡せます。
https://docs.angularjs.org/api/ngMessages
フォームのエラーメッセージの表示対応について、
従来のng-showやng-ifで行う方法では、同時に複数エラーが出た場合にもひとつだけ表示するような制御が面倒でしたが、
ngMessagesを利用すると、ずいぶん簡単に記述できるようになりました。
ngMessagesを利用するには別途angular-messages.jsを読み込む必要があります。
これらの機能を利用したフォームのサンプルを作ってみました。
※ユーザー名:「aaaa」「bbbb」「cccc」を重複エラーとしています。
今回のバージョンアップで、ますます使いやすくなったと思いますので、
ぜひみなさん試してみてはいかがでしょうか。
先日、AngularJS 1.3 がリリースされましたね。
動作速度の改善や、メモリ消費量の削減などパフォーマンス面での改善もうれしいところですが、
機能的にはフォーム関連の機能強化が特にうれしく感じましたので、紹介したいと思います。
■ ngModel.$validators
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
ngModel.$validators を使うと、独自のバリデーション関数を簡単に定義することができるようになりました。
以下の例のように、入力値を引数で受け取り、返り値で真偽値を返す関数を$validatorsオブジェクトに定義します。
$validatorsのキー(ここではvalidCharacters)が、エラーメッセージ表示時などの参照用に利用できます。
- ngModel.$validators.validCharacters
= function(modelValue, viewValue) { - var
value = modelValue || viewValue; - return
/[0-9]+/.test(value) && /[a-z]+/.test(value) && /[A-Z]+/.test(value) && /\W+/.test(value); - };
■ ngModel.$asyncValidators
サーバへの問い合わせが必要な場合など、非同期の処理がある場合のバリデーションにも対応しています。
以下の例のように、ngModel.$asyncValidators にpromiseを返す関数を定義します。
- ngModel.$asyncValidators.uniqueUsername
= function(modelValue, viewValue) { - var
value = modelValue || viewValue; - //
Lookup user by username - return
$http.get('/api/users/' + value). then(function resolved() { //username exists, this means validation fails return $q.reject('exists'); }, function rejected() { //username does not exist, therefore this validation passes return true; }); - };
今まで、パスワード確認入力やユーザー名重複のサーバ問い合わせなど、
controller(service)の値を組み合わせたバリデーションをdirectiveで
指定するのが面倒で、ui-validateというcontrollerの関数定義を指定できる外部モジュールを利用していました。
ui-vaidateでも今まで対応できていたので、別にそれでもいいといえばいいのですが、
しかし、1.3からは以下のような汎用的なdirectiveを定義すれば、とても素直に指定することができます。
- app.directive('appValidators',
function () { return { require: 'ngModel', scope: { appValidators: '=', }, link: function (scope, elem, attrs, ctrl) { var validators = scope.appValidators || {}; angular.forEach(validators, function (val, key) { ctrl.$validators[key] = val; }); } }; - });
- app.directive('appAsyncValidators',
function () { return { require: 'ngModel', scope: { appAsyncValidators: '=' }, link: function (scope, elem, attrs, ctrl) { var asyncValidators = scope.appAsyncValidators || {}; angular.forEach(asyncValidators, function (val, key) { ctrl.$asyncValidators[key] = val; }); } }; - });
利用時は以下のように、controllerでオブジェクトをテンプレート側のapp-validators属性で渡せます。
- app.controller('AppCtrl',
function($scope) { $scope.user_name_validators = { hoge: function (modelValue, viewValue) { var val = modelValue || viewValue; return val == 'hoge'; }, fuga: function (modelValue, viewValue) { var val = modelValue || viewValue; return val == 'fuga'; } }; - });
- <input
type="text" ng-model="user_name" app-validators="user_name_validators">
■ ngMessages
https://docs.angularjs.org/api/ngMessages
フォームのエラーメッセージの表示対応について、
従来のng-showやng-ifで行う方法では、同時に複数エラーが出た場合にもひとつだけ表示するような制御が面倒でしたが、
ngMessagesを利用すると、ずいぶん簡単に記述できるようになりました。
ngMessagesを利用するには別途angular-messages.jsを読み込む必要があります。
- <script
src="angular.js"></script> - <script
src="angular-messages.js"></script> - <form
name="myForm"> - <input
type="text" ng-model="field" name="myField" required minlength="5" /> - <div
ng-messages="myForm.myField.$error"> <div ng-message="required">You did not enter a field</div> <div ng-message="minlength">The value entered is too short</div> - </div>
- </form>
■サンプル
これらの機能を利用したフォームのサンプルを作ってみました。
※ユーザー名:「aaaa」「bbbb」「cccc」を重複エラーとしています。
- <!DOCTYPE
html> - <html
lang="en" ng-app="app"> <head> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular-messages.js"></script> <script src="app.js"></script> <style> .container { margin-top: 50px;} </style> </head> <body ng-controller="AppCtrl"> <div class="container"> <div class="row"> <div class="col-xs-6"> <form name="userForm" novalidate ng-submit="submit()"> <div class="form-group" ng-class="{'has-error': userForm.user_name.$dirty && userForm.user_name.$invalid}"> <label class="control-label">ユーザー名</label> <span class="help-inline text-danger" ng-messages="userForm.user_name.$error" ng-if="userForm.user_name.$dirty || userForm.$submitted"> <span ng-message="required">必須です</span> <span ng-message="pattern">不正な値です</span> <span ng-message="minlength">4文字以上</span> <span ng-message="duplicate">既に利用されているユーザー名です</span> </span> <input type="text" class="form-control" name="user_name" ng-model="user.user_name" required minlength="4" ng-pattern="/^[a-zA-Z0-9]+$/" app-async-validators="asyncValidators.user_name" /> <p class="help-block">※必須、英数字4文字以上</p> </div> <div class="form-group" ng-class="{'has-error': userForm.password.$dirty && userForm.password.$invalid}"> <label class="control-label">パスワード</label> <span class="help-inline text-danger" ng-messages="userForm.password.$error" ng-if="userForm.password.$dirty || userForm.$submitted"> <span ng-message="required">必須です</span> <span ng-message="minlength">4文字以上</span> <span ng-message="joe">ユーザー名と一緒はダメー</span> </span> <input type="password" class="form-control" name="password" ng-model="user.password" required minlength="4" app-validators="validators.password" /> <p class="help-block">※必須、4文字以上</p> </div> <div class="form-group" ng-class="{'has-error': userForm.password_confirm.$dirty && userForm.password_confirm.$invalid}"> <label class="control-label">パスワード(確認)</label> <span class="help-inline text-danger" ng-messages="userForm.password_confirm.$error" ng-if="userForm.password_confirm.$dirty || userForm.$submitted"> <span ng-message="required">必須です</span> <span ng-message="confirm">パスワード確認が一致しません</span> </span> <input type="password" class="form-control" name="password_confirm" ng-model="user.password_confirm" required minlength="4" app-validators="validators.password_confirm" /> </div> <button class="btn btn-primary btn-block" ng-disabled="userForm.$dirty && userForm.$invalid">送信</button> </form> </div> <div class="col-xs-6"> <pre>user = {{user|json}}</pre> <pre>userForm.$error = {{userForm.$error|json}}</pre> </div> </div> </div> </body> - </html>
- (function()
{ 'use strict'; var app = angular.module('app', ['ngMessages']); app.controller('AppCtrl', function ($scope, $q) { // モデル $scope.user = {}; // バリデータ $scope.validators = { password: { // ユーザー名とパスワードは一緒はダメ joe: function (modelValue, viewValue) { var val = modelValue || viewValue; var user = $scope.user || {}; return val != user.user_name; } }, password_confirm: { // パスワード確認 confirm: function (modelValue, viewValue) { var user = $scope.user || {}; var val = modelValue || viewValue; return val == user.password; } } }; // 非同期バリデータ $scope.asyncValidators = { user_name: { duplicate: function (modelValue, viewValue) { var users = ['aaaa', 'bbbb', 'cccc']; var val = modelValue || viewValue; return $q(function (resolve, reject) { setTimeout(function () { if (users.indexOf(val) === -1) { resolve('ok'); } else { reject('ng'); } }, 1000); }); } } }; // user_name != password判定のため $scope.$watch('user.user_name', function() { $scope.userForm.password.$validate(); }); // password == password_confirm判定のため $scope.$watch('user.password', function() { $scope.userForm.password_confirm.$validate(); }); // 送信ボタンイベント $scope.submit = function () { // 何も変更しないで、送信ボタン時にエラーを表示してあげる if ($scope.userForm.$invalid) { $scope.userForm.$setDirty(); return; } // 成功!! console.log($scope.user); alert('成功'); }; }); /** * validators */ app.directive('appValidators', function () { return { require: 'ngModel', scope: { appValidators: '=', }, link: function (scope, elem, attrs, ctrl) { var validators = scope.appValidators || {}; angular.forEach(validators, function (val, key) { ctrl.$validators[key] = val; }); } }; }); /** * asyncValidators */ app.directive('appAsyncValidators', function () { return { require: 'ngModel', scope: { appAsyncValidators: '=' }, link: function (scope, elem, attrs, ctrl) { var asyncValidators = scope.appAsyncValidators || {}; angular.forEach(asyncValidators, function (val, key) { ctrl.$asyncValidators[key] = val; }); } }; }); - })();
今回のバージョンアップで、ますます使いやすくなったと思いますので、
ぜひみなさん試してみてはいかがでしょうか。