inputタグname属性の制約について

困っていること
Angularの双方向データバインディングを利用する際に、[(ngModel)]を付与するinputタグのname属性は、一意にすることが必須でしょうか。
template→component方向のバインドはうまくいくのですが、component→templateのバインドがうまくいかず(同じname属性をもつ要素全てに同じ値が表示されてしまう。)困っています。
name属性が重複していても正しくバインドできるノウハウをお持ちの方がいらっしゃれば、ご教示頂けると幸甚です。

環境
Angular 8
TypeScript 3.4.5

NgFormの値として利用したい場合は一意にすることが必須です。

nameはフォームの値をjsonにした時、
オブジェクトのキーとなることを考えれば全ての値が同じになってしまうような不具合が起こることは想像に難くないと思います。(記述によっては不具合が露見しない場合もあります)

同一のnameを利用したい理由によって対応は変わってきますが、以下のような手法が考えられるかと思います。

a) [ngModelOptions] を利用する

input要素に[ngModelOptions]="{ standalone: true }"を付与することで、
NgForm内の値ではないことを明示的に示すことができます。

こうすることで、NgFormとコントロールのヒモ付が解除され、nameが持つAngular的な文脈も消滅します。

<form (ngSubmit)="f.value" #f="ngForm"> ... のようなコードを書いていなければ有効でしょう。(f.valueに該当コントロールのvalueが入ってこなくなります)

b) name値をユニークにする

例えばngForで項目を繰り返すことによってnameが重複する設計になっているような場合、
以下のような記述で問題は解決されるかと思います。

<input type="text" name="value_{{ i }}" [(ngModel)]="item.value" *ngFor="let item of items; index as i>

ただ、こういう場合はname属性になんの意味もない場合が多いはずですので、
特にname属性が意味を持たないのであれば[ngModelOptions]を付与してname属性は記述しないことをオススメします。

c) ngFormをテンプレートで参照せず、使わない form内でngModelを利用しない

そもそも ngForm form要素の中でngModelを利用するからこういうことになるのであって、
おもむろにngModelを利用する分には問題ないはずです。

おそらくinput要素の親階層#f="ngForm"みたいな記述があるがform要素になっているはずなので、ngFormを利用していないのであればそれを消しましょう。
(ngFormを利用している場合はもちろんエラーになります)

form要素を除外してしまいしょう。

ngForm formの中に入れ子にしないことで、
そもそもname属性を省いてngModelを利用することができるようになります。

d) ngModelGroup を利用する

NgFormとのヒモづけをしたまま使いたい場合、ngModelGroupでくくる必要があります。

<form #f="ngForm">
  <ng-container [ngModelGroup]="i" *ngFor="let item of items">
    <input name="value" [(ngModel)]="item.value">
  </ng-container>
</form>
// `items = [{ value: "hoge" }, { value: "foo" }]`だった時、`f.value`の値は以下のようになる
// "0"と"1"は`ngModelGroup`の値
{
  "0": { value: "hoge" },
  "1": { value: "foo" }
}

どの手法を取るのが最適かについては情報が少ないのでなんとも言えませんが、a)かd)になるかと思います。
Stackblitzなどで再現コードを作成すると良いでしょう。


補足

d-okuさんのこちらの解答は私の解答でいう c) パターンですが、仮に親要素にformをおいても不具合はないように見えるかと思います。
このコードでは、初期値を準備すると問題が露見するようになります。

2 Likes

<input name=“ここの名前が一意”>ということでしょうか?
こちらは一意でなくとも、ngmodelには影響がないかと思います。
以下例です。 app.component.htmlで同一nameのinputがあります。

1 Like

akaiさん
非常にご丁寧なご説明ありがとうございます。
今回ngFormを利用する想定はなかったため、 a) [ngModelOptions] を利用するの対応で事象が解決することを確認できました。
ありがとうございました。

1点気になることがあります。
input要素を囲んでいたformタグ自体を消すことでも事象が解決しました。
特にformタグには#f="ngForm"のような記載はしていなかったのですが、formタグを消すことでも事象が解決する理由はなぜなのかがよく分かりません。
#f="ngForm"のような記載がなくても、formタグが存在するだけでデフォルトでngFormとして扱われるものなのでしょうか?
formタグの記載は以下の通りでした。

<form method="POST" name="MainForm" autocomplete="off">

1 Like

d-okuさん
ご回答ありがとうございます。
akaiさんのご回答と併せて解決することができました。
ありがとうございました。

#f="ngForm"のような記載がなくても、formタグが存在するだけでデフォルトでngFormとして扱われるものなのでしょうか?

そんなことはないはずと思って以下のコードで確認しましたが、そんなことありましたね。

親要素にform要素があるだけで、コントロール(NgModel)にformDirective(NgForm)が付与されています。
(解答時はされないと思っていた)

よく考えたら#f="ngForm"はディレクティブでなくて、
exportAsされたngFormを関連付けするためだけのものなので、それ自体がngFormを生み出すとかではなく、
ngModelを子に持つform要素自体が、NgFormのインスタンスを持つような仕組みになってるんですね。

解答の方、少し修正しておきます。

1 Like

ところで、これまでの会話を踏まえて、
d-okuさんのコードをお借りして問題のあるコードを作ってみました。

初期値がどちらもfooになっていますね。

変更した箇所は

  • ngModelの親要素をform要素に
  • input1とinput2にそれぞれ別の初期値を与える

と言ったものです。


再現方法はわからないのですが、リアクティブに入力が同期されてしまう不具合とかを出した記憶もあるので、
テンプレートドリブンフォームを利用する際はnameに気をつけましょう。

2 Likes

@akai

あまりテンプレートエンジンを使っていない&nameを使わなかったので気づいてませんでした。
一緒に勉強になりました。ありがとうございます。