AngularJSでフォームを入れ子にしたときに内側のフォームを非表示にしてバリデーションをスキップする術

こんにちは。ワインをたくさん飲みました。きたけーです。

AngularJSでフォームを入れ子にしたときの内側のフォームのバリデーションをスキップする術を知ったのでメモします。

HTMLのタグの仕様では、formタグはネストできないことになっていますが※1、
AngularJSのng-formディレクティブはネストできます ※2。

※2に書かれているように外側のフォームがvalidということは内側のフォームが全てvalidということです。

入力項目が多いフォームでは、入力項目のまとまりごとにバリデーションをおこなうためにフォームを分割してネストさせますが、入力された内容によっては内側のフォームを非表示にして、そのフォームのバリデーションをスキップさせたいときがあります。

そのような場合は以下のようにng-ifディレクティブで表示・非表示を切り替えることで実現できます。 こんなかんじです。

<form name="alphaForm" novalidate>
  <input type="text" placeholder="Name" name="name" ng-model="user.name" required />

  <div ng-messages="alphaForm.name.$error" ng-if="alphaForm.$dirty">
    <div ng-message="required">入力してください</div>
  </div>

  <ng-form name="betaForm" ng-if="somethingCondition()">
    <input type="text" placeholder="Age" name="nage" ng-model="user.age" />
    <div ng-messages="betaForm.age.$error" ng-if="betaForm.$dirty">
      <div ng-message="required">入力してください</div>
    </div>
  </ng-form>

  <input type="submit" ng-disabled="alphaForm.$invalid">Submit</input>
</form>

入れ子になっているbetaFormをng-ifディレクティブで制御しています。somethingConditionはスコープに生えた真偽値を返す適当なメソッドです。

このとき、somethingConditionが偽を返せば、betaFormは非表示になり、名前だけ入力すれば、alphaFormのバリデーションが通り、submitボタンを押すことができます。
逆にsomethingConditionが真を返せば、betaFormが表示され、年齢も入力しなければ、alphaFormのバリデーションは通らないので、submitボタンが無効になり、押せません。

単純に表示・非表示でバリデーションをスキップするかどうか切り替えれるわけではなく、ng-ifディレクティブの代わりにng-showディレクティブを使うと、somethingConditionが偽のときに、名前を入力してもバリデーションは通りません。

ng-ifng-showの違いは表示・非表示をHTMLの要素レベルでおこなうか、スタイルシートのプロパティレベルでおこなうかですが、この挙動の違いは追々、Angular本体のFormController, NgModelControllerコードを読んで調べてみようと思います。


※1 フォームコントロールはform要素内に書きましょう - Web標準普及プロジェクト

またform要素の閉じタグは必須ですし、form要素の入れ子(ネスト)は禁止されています。

※2 https://docs.angularjs.org/api/ng/directive/form

In Angular forms can be nested. This means that the outer form is valid when all of the child forms are valid as well.