AngularJS + TypeScript でリッチなフォームを作ってみる
前回(AngularJS で Hello World)に引き続き AngularJS + TypeScript による簡単なアプリケーションを書いてみました。
今回は AngularJS を使ったフォームの例です。 AngularJS のみで、比較的簡単にリッチなフォームが作成できることがわかるんじゃないかなぁと思います。
まえおき
- TypeScript で書いてます。
- AngularJS のバージョンは2013年2月現在の最新安定版である 1.0.4 を使用します。
- 自動受け入れテスト(E2E Test)は面倒なので省略しました。
ソースコードについて
なお、今回のソースコード一式も GitHub で公開しています。 また、こちらで実際に動作を確認することもできます。
解説
前回解説した内容についてはなるべく割愛します。
form 要素
<form id="signupForm" name="form" ng-controller="SignupForm.Controller" ng-submit="submit()" novalidate>
form 要素にいくつか属性を指定します。
- スタイルシートでスタイルを指定するために id を指定しています。
- AngularJS の FormController 機能を使うために name を指定しています。
- ng-controller 属性で対応する Controller を指定しています。
- ng-submit 属性 でフォームが submit される時の処理を指定しています。
- ng-submit 属性を指定するとブラウザ標準の処理(送信)がキャンセルされ、 指定された処理のみが実行されるようになります。
- novalidate 属性 は AngularJS と HTML5 の validation が衝突しないように指定しています。 AngularJS でフォームを制御する場合は書いた方がいいみたい。
テキストフィールド
<input type="text" name="username" ng-model="data.username" ng-minlength="8" ng-maxlength="16" ng-pattern="/^[a-zA-Z0-9]*$/" required>
シンプルなテキストフィールド。やっぱり見慣れない属性が並んでいます。
- ここで指定した name 属性が FormController で使用されます。
- ng-minlength, ng-maxlength, ng-pattern, required の各属性で
AngularJS による validation を定義しています。
- ng-minlength, ng-maxlength はその名の通り、 テキストフィールドに入力できる最小・最大の文字数を指定できます。
- ng-pattern 属性 を使うことで、入力できるテキストを正規表現で制限することができます。
- 今回はベタ書きしてますが、Scope内の変数を参照・指定することもできます。
- required 属性 は AngularJS 独自の属性ではありませんが、 これを指定することで AngularJS 側でも必須と認識されます。
- validation の状況に応じて
ng-valid
,ng-invalid
,ng-dirty
などの class が自動で付与されます。 これは実際に動いている状態で確認してみるのが早いと思います。
Validation と ng-show 属性
<span class="error" ng-show="form.username.$error.pattern"> 半角英数字のみで入力してください </span> <span class="error" ng-show="form.username.$error.minlength"> 8文字以上で入力してください </span> <span class="error" ng-show="form.username.$error.maxlength"> 16文字以内で入力してください </span> <span class="error" ng-show="form.username.$error.required"> 入力してください </span> <span class="valid" ng-show="form.username.$valid"> OK </span>
フォームの各コントロールに validation ルールを定義することで 上記の例のように validation 結果をリアルタイムで参照できるようになります。
今回は validation 結果を ng-show 属性で参照することにより、validation 結果に対応してメッセージを表示するようにしています。 このように ng-show 属性 を使うことで「記述した条件が true の場合のみ表示する」処理をかんたんに実現できます。
パスワード
<input type="{{textOrPassword()}}" name="password" ng-model="data.password" ng-minlength="8" ng-maxlength="16" ng-pattern="/^[a-zA-Z0-9]*$/" required> (中略) <input type="checkbox" id="passwordVisible" ng-model="passwordVisible"> <label for="passwordVisible">パスワードを表示する</label>
$scope.textOrPassword = () => { return $scope.passwordVisible ? 'text' : 'password'; };
パスワード入力フィールドの type 属性を動的にし、その値が checkbox の状態によって変わるようにすることで パスワードの表示・非表示を切り替える最近よくあるUIが比較的かんたんに実現できています。
フォームの入れ子
<p class="form-question">氏名:</p> <div ng-form name="nameForm" class="form-answer"> <input type="text" name="lastName" ng-model="data.name.lastName" class="name" placeholder="姓" required> <input type="text" name="firstName" ng-model="data.name.firstName" class="name" placeholder="名" required> <span class="error" ng-show="nameForm.$error.required"> 入力してください </span> <span class="valid" ng-show="nameForm.$valid">OK</span> </div>
任意の要素に ng-form 属性 を付与することでフォームの入れ子構造が実現できます。 今回は氏名の「姓」と「名」を1つの子フォーム nameForm としています。 これにより複数要素の validation 結果を1つの子フォームの validation 結果として参照できるようになっています。
プルダウン
<select name="lang" ng-model="data.lang" ng-options="x.id as x.label for x in langs" required> <option value="">----------------</option> </select>
ng-options 属性 で option 要素を動的に生成しています。
今回は lang
に格納されている配列から option 要素の value となる id
と表示するテキストの label
を抽出しています。
ng-options 属性の書き方は他にもいくつかパターンがあるので
公式ドキュメントを読んでみてください。
ラジオボタン
<li ng-repeat="x in colors"> <input type="radio" id="browser-{{x.id}}" value="{{x.id}}" ng-model="data.color"> <label for="browser-{{x.id}}">{{x.label}}</label> </li>
ng-repeat 属性 を使うと、配列(またはオブジェクト)を使用して要素を繰り返し動的に生成することができます。
今回はあらかじめ定義された配列 colors
を使用して、動的にラジオボタンを生成しています。
ラジオボタンでは Model の状態に応じてチェック・非チェックが自動で制御されます。
チェックボックス
<li ng-repeat="x in ballGames"> <input type="checkbox" id="ballGame-{{x.id}}" value="{{x.id}}" ng-model="data.ballGame[x.id]"> <label for="ballGame-{{x.id}}">{{x.label}}</label> </li>
ラジオボタン同様 ng-repeat 属性を使用してチェックボックスを動的に生成しています。 チェックボックスのチェック状況(ON/OFF)に応じて対応する Model の値が true/false に設定されます。
メールアドレス
<input type="email" name="email" ng-model="data.email" required> <span class="error" ng-show="form.email.$error.email"> 正しいメールアドレスを入力してください </span>
入力がメールアドレスであるかどうかの判定も AngularJS がやってくれます。
URL
<input type="url" name="url" ng-model="data.url"> <span class="error" ng-show="form.url.$error.url"> 正しいURLを入力してください </span>
同様にURLかどうかの判定もできます。この辺は HTML5 だけでも同じことができますが AngularJS を使うことでよりリッチに、ブラウザ間の差異なく実現することができます。
フォームの繰り返し
<div ng-form name="phoneForm" class="phone" ng-repeat="phone in data.phone"> <a href="" class="remove" ng-click="removePhone($index)">×</a> <select name="phoneType" ng-model="phone.type" ng-options="x.id as x.label for x in phoneTypes" required> </select> <input type="text" name="phoneNumber" class="phoneNumber" placeholder="電話番号" ng-model="phone.number" ng-minlength="10" ng-maxlength="11" ng-pattern="/^\d+$/" required> <span class="error" ng-show="phoneForm.phoneNumber.$error.pattern"> 半角数字のみで入力してください </span> <span class="error" ng-show="phoneForm.phoneNumber.$error.minlength"> 10桁以上で入力してください </span> <span class="error" ng-show="phoneForm.phoneNumber.$error.maxlength"> 11桁以内で入力してください </span> <span class="error" ng-show="phoneForm.phoneNumber.$error.required">入力してください</span> <span class="valid" ng-show="phoneForm.phoneNumber.$valid">OK</span> <span class="note">例)0312345678</span> </div> <div class="actions"> <a href="" class="action" ng-click="addPhone()">電話番号を追加</a> </div>
ng-repeat 属性と ng-form 属性を組み合わせて同じフォーム要素の繰り返しを実現しています。 こういった複雑なフォームをかんたんに実現できるのも AngularJS の魅力のひとつだと思います。
ng-disabled 属性
<button ng-disabled="form.$invalid">送信</button>
ng-show 属性同様「特定の条件を満たす場合のみ disabled」というコントロールを実現できます。 今回はフォーム全体の validation 結果が invalid である場合に送信ボタンを disabled にしています。
課題とか
今回は AngularJS の機能を使ってリッチなフォームを作ってみましたが、いくつか課題もあります。
- 記述が長い
- ViewHelper 的なオブジェクトを作ってなんとか記述を減らしたい。
- サーバーとの通信(フォームデータの送信)は?
- 今回はあえて省いた。
$http
という AngularJS のサービスがあるので、それを使えば実現できる。
- 今回はあえて省いた。
- 複雑な validation が定義できない?
- 他のコントロールに依存する validation など、複雑な validation が実現しにくい。
- ng-required 属性を使えば「特定の条件を満たす場合のみ必須」といった条件は実現できる。
- AngularUI を使えば好きな validation を定義できるようになるっぽい。