みんなのちからになりたい

コピペでブログラムつくっていきたい

AngularのFormGroupでresetしてもmat-form-fieldのinvalidがリセットされない件

件名そのままなんだけど、submitしたあとにform.reset()してもinvalidが消えないので困っていた。

そんなわけある?と思ってググるとそんなわけあるようで、こんな記事を見つけた。

github.com

まさにこれなんだけど、2017年なのでさすがにv18にもなってそんなわけないだろうと思って下にスクロールしていくと現代でも解決してないようでした。

結局どうやって解決したかは以下。

mat-form-field does not remove mat-form-field-invalid class on FormGroup reset · Issue #4190 · angular/components

<form [formGroup]="myForm" #formDirective="ngForm" (ngSubmit)="submitForm(formDirective)">
private submitForm(formDirective: FormGroupDirective): void {
    formDirective.resetForm();
    this.myForm.reset();
}

なんでこうなるかの理由が以下にある

mat-form-field does not remove mat-form-field-invalid class on FormGroup reset · Issue #4190 · angular/components

以下4oくんの解説


解説

1. novalidate 属性の必要性

  • 要点: フォームに novalidate 属性を付けないと、ブラウザのネイティブなバリデーションが働き、Angular の期待する動作と食い違うことがあります。novalidate を使用することで、Angular のバリデーションロジックを優先し、予期せぬ動作を防ぐことができます。

2. Angular と DOM の相互作用

  • 課題: Angular の FormGroup は、DOM イベント(フォームの送信やバリデーションの失敗)に関する情報を直接管理していません。FormGroup は純粋な Angular の構造で、DOM API とは無関係です。
    • そのため、フォーム送信後に FormGroup#reset() を呼んでも、送信状態(submitted のフラグ)がリセットされない場合があります。
    • これは、Angular の FormGroupFormGroupDirective の役割分担によるものです。FormGroupDirective が DOM と連携し、FormGroup がその上に構築されています。

3. DOM イベントとフォーム送信の動作

  • フォーム送信イベントの動作: <form> タグに対して submit イベントが発生する際、ボタンやフィールド自体にはイベントが発生しません。これは、ブラウザのデフォルト動作に由来します。
    • <button> タグのデフォルトは type="submit" であり、novalidate を付けないとフォーム送信が行われ、ネイティブのバリデーションが実行されます。バリデーションに失敗した場合、フォームは送信されず、submit イベントも発生しません。

4. HTML の問題点

  • エラーの原因: <button> タグのデフォルト動作により、フォームが送信され、ネイティブなバリデーションが実行されます。これにより、FormGroupDirective で管理されている Angular のバリデーションとは別の動作が発生します。
    • これが、意図しないエラーメッセージやフォーム送信の原因となります。

5. エラーの解決策

  • ベストプラクティス: <form> タグに novalidate を付け、ボタンに type="button" を指定することで、フォーム送信を防ぐことができます。
    • <button mat-button type="button">ボタンラベル</button>
    • <input mat-button type="button" value="ボタンラベル">
    • これにより、ネイティブの submit イベントによる不具合を回避できます。

6. 例外的なケースへの対応

  • 特殊なケース: ネイティブの submit/invalid イベントサイクルを利用する場合は、NgFormFormGroupDirective を利用してフォームの送信状態を管理する必要があります。
    • 具体的には、NgFormコンポーネントに注入するか、FormGroupDirective を使って DOM のフォームオブジェクトを resetForm() でリセットします。これにより、DOM と Angular のフォーム状態が同期します。

まとめ

Angular でフォームのバリデーションと送信を適切に管理するためには、novalidate の使用が推奨されます。これにより、ネイティブバリデーションによる予期せぬ動作を防ぎ、Angular のバリデーションロジックを優先させることができます。また、必要に応じて NgFormFormGroupDirective を使ってフォームの状態を制御することが、最も確実な方法です。

この解説により、Angular フォームの内部で発生している複雑な相互作用と、それに伴う問題点をより深く理解できるでしょう。


novalidateはまあどうでもよくて、要は

  • FormGroup は純粋な Angular の構造で、DOM API とは無関係
  • なのでFormGroup#reset() を呼んでも、送信状態(submitted のフラグ)がリセットされない(場合があると書いてあるが実際にリセットされない)
  • FormGroupDirective は DOM と連携してるので、こいつでresetすればブラウザ動作のresetがされる(要はtype="reset"と同じ動作)(ViewChildでやってもたぶんいけると思う)

なので、FormGroupDirective 使ってFormをリセットしたらちゃんと動作した