読者です 読者をやめる 読者になる 読者になる

パンダのメモ帳

技術系のネタをゆるゆると

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 を定義できるようになるっぽい。

参考URL