AngularJSでフォームを作成すると、バリデーションが色々いじれて便利です。
ここまで色々出来るようになってくるとJS側でかなりガッツリバリデーションを済ませてしまって登録のHTTPレスポンスは正常応答のみを前提として、異常応答は極端な処理ですませる…としたくなるところです。
ただちょっと変わったバリデーションをかけようとするとたちまちうまく行かなくなるのがフレームワーク系の辛い所。
今回は、入力されたログインIDが利用可能かの確認をDBに問い合わせる、というバリデーションをやってみます。
とりあえずコード
<input type="text" class="pure-u-1" name="login_id" id="form_login_id"
ng-model="post.login_id" ng-pattern="/^[0-9a-zA-Z_]+$/"
ng-maxlength="10" ng-change="validateLoginId()" required/>
<div class="panel-red" ng-show="joinForm.login_id.$error.pattern">
ログインIDは半角英数字で入力してください。
</div>
<div class="panel-red" ng-show="joinForm.login_id.$error.unique">
そのログインIDは使用できません。
</div>
<div class="panel-green"
ng-show="joinForm.login_id.$dirty &&!joinForm.login_id.$error.inquired &&!joinForm.login_id.$error.unique">
そのログインIDは使用可能です。
</div>
<div class="panel-red" ng-show="joinForm.login_id.$error.maxlength">
ログインIDは10文字以内で入力してください。
</div>
んでngChangeは以下の様に実装する。
$scope.validateLoginId = ()->
$scope.joinForm.login_id.$setValidity "inquired",false #DB照会待ちエラーのセット
startVal = $("#form_login_id").val()
setTimeout ->
do (val = startVal) ->
if val == $("#form_login_id").val()
Config.log "test for #{val} #{$("#form_login_id").val()}"
# http発行
result = $http(
method: "POST"
url: "#{Config.api}user/valid_user_id/#{val}"
)
result.success (data,status,headers,config) ->
$scope.joinForm.login_id.$setValidity "inquired",true
$scope.joinForm.login_id.$setValidity "unique",true
result.error (data,status,headers,config) ->
$scope.joinForm.login_id.$setValidity "inquired",true
$scope.joinForm.login_id.$setValidity "unique",false
,1000
joinForm
はフォーム名、Config.log
はconsole.log
のラッパー、Config.api
はAPIドメインです。
コピペで動くかは微妙。参考程度に。
ポイント
エラーのセット
今回はinquired
とunique
の二つのエラーを登録しています。
- inquired: DBへの照会を済ませているかのチェック
- unique: DBにいまだ登録されていないキーであることのチェック
DBに確認しにいく、という処理の都合上、この二つのエラーをきっちりわけないと
DB確認中の数秒間はバリデーションOKになってしまう…という問題が発生します。
angularのエラーのセットは$setValidity
で。form.$setValidity
としてもいいし、form.elem.$setValidity
としてもいい。同じエラー名で複数の要素のエラーを判別したいなら後者の方がオススメです。
ng-changeの問題
ng-clickは$event
がとれるのに、ng-changeは$eventが取れない。ので、ng-change内は上記の用にIDベタ書きで要素取得するしかなさそうです。
あと一文字一文字の変更毎にng-changeが発火してしまうので、setTimeoutを使って1秒間変動がなければ、という条件をつけています。処理フロー的には
- フォーム内容の変更
- 問い合わせ済みエラーの
inquired
を発行 - フォームデータの保存
- 1秒待つ
- フォームデータの取得&&1秒前のフォームデータ(3.で保存)と比較
- 変化がなければDBへの問い合わせを実行
- DBからの応答を受け取ったら、問い合わせ済みエラーの
inquired
を削除 - 応答でNGとの内容なら重複エラーの
unique
を発行
みたいな感じです。
気になる点
DB問い合わせ応答の順番入れ替わりについて
無数にサーバ問い合わせを行うと、順番が入れ替わって結果が返ってくる事がある。
今回の処理では1回めの問い合わせから2回めの問い合わせまでには少なくとも1秒の猶予があるので、1秒以内にDB問い合わせが帰ってくれば順番の入れ替わりについての心配は無いのだけれども…
きっちり処理を固めるなら多分そのへんに手をいれることになるのか…
ng-showがごちゃごちゃする点について
イライラする。