このチュートリアルでは、テンプレート駆動型フォームの作成方法について説明します。フォーム内のコントロール要素は、入力検証を含むデータプロパティにバインドされています。入力検証は、データの整合性を維持し、ユーザー体験を向上させるためのスタイリングに役立ちます。
テンプレート駆動型フォームは、双方向データバインディングを使用して、テンプレートでの変更が加えられたときにコンポーネントのデータモデルを更新し、その逆も同様に行います。
テンプレートフォームとリアクティブフォーム
Angularは、インタラクティブフォームのために2つの設計アプローチをサポートしています。テンプレート駆動型フォームでは、Angularテンプレートでフォーム固有のディレクティブを使用できます。リアクティブフォームは、フォーム構築のためのモデル駆動型アプローチを提供します。
テンプレート駆動型フォームは、小規模または単純なフォームに適していますが、リアクティブフォームはよりスケーラブルで、複雑なフォームに適しています。2つのアプローチの比較については、アプローチの選択を参照してください。
Angularテンプレートを使用して、ログインフォーム、コンタクトフォーム、ほとんどのビジネスフォームなど、あらゆる種類のフォームを構築できます。 コントロールをクリエイティブにレイアウトし、オブジェクトモデル内のデータにバインドできます。 検証ルールを指定し、検証エラーを表示し、特定のコントロールからの入力を条件付きで許可します。そして、組み込みの視覚的なフィードバックをトリガーし、その他多くのことができます。
目標
このチュートリアルでは、次の方法について学びます。
- コンポーネントとテンプレートを使用してAngularフォームを構築する
- ngModelを使用して、入力コントロールの値を読み書きするための双方向データバインディングを作成する
- コントロールの状態を追跡する特別なCSSクラスを使用して、視覚的なフィードバックを提供する
- ユーザーに検証エラーを表示し、フォームの状態に基づいてフォームコントロールからの入力を条件付きで許可する
- テンプレート参照変数を使用して、HTML要素間で情報を共有する
テンプレート駆動型フォームの構築
テンプレート駆動型フォームは、FormsModuleで定義されたディレクティブに依存します。
| ディレクティブ | 詳細 | 
|---|---|
| NgModel | アタッチされたフォーム要素の値の変更とデータモデルの変更を調整し、入力検証やエラー処理を使用してユーザー入力に対応できるようにします。 | 
| NgForm | トップレベルの FormGroupインスタンスを作成し、<form>要素にバインドして、集計されたフォームの値と検証の状態を追跡します。FormsModuleをインポートすると、このディレクティブはすべての<form>タグでデフォルトでアクティブになります。特別なセレクターを追加する必要はありません。 | 
| NgModelGroup | DOM要素に FormGroupインスタンスを作成してバインドします。 | 
ステップの概要
このチュートリアルでは、次の手順を使用して、サンプルフォームをデータにバインドし、ユーザー入力を処理します。
- 基本的なフォームを構築する。
  - サンプルデータモデルを定義する
- FormsModuleなど、必要なインフラストラクチャを含める
 
- ngModelディレクティブと双方向データバインディング構文を使用して、フォームコントロールをデータプロパティにバインドする。- ngModelがCSSクラスを使用してコントロールの状態を報告する方法を調べる
- コントロールに名前を付けて、ngModelからアクセスできるようにする
 
- ngModelを使用して、入力の有効性とコントロールの状態を追跡する。- 状態に基づいて視覚的なフィードバックを提供するためのカスタムCSSを追加する
- 検証エラーメッセージを表示および非表示にする
 
- モデルデータに追加することで、ネイティブHTMLボタンのクリックイベントに対応する。
- フォームのngSubmit出力プロパティを使用して、フォームの送信を処理する。- フォームが有効になるまで、Submitボタンを無効にする
- 送信後、ページ上の異なるコンテンツに、完了したフォームを交換する
 
フォームの構築
- 提供されたサンプルアプリケーションは、フォームに反映されるデータモデルを定義する - Actorクラスを作成します。- src/app/actor.ts- export class Actor { constructor( public id: number, public name: string, public skill: string, public studio?: string, ) {}}
- フォームのレイアウトと詳細は、 - ActorFormComponentクラスで定義されます。- src/app/actor-form/actor-form.component.ts (v1)- import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////}- コンポーネントの - selector値は"app-actor-form"であるため、このフォームを- <app-actor-form>タグを使用して親テンプレートにドロップできます。
- 次のコードは、新しいアクターインスタンスを作成し、初期フォームにサンプルアクターを表示します。 - import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////}- このデモでは、 - modelと- skillsのダミーデータを使用しています。 実際のアプリでは、データサービスを注入して実際のデータを取得して保存するか、これらのプロパティを入力と出力として公開します。
- The component enables the Forms feature by importing the - FormsModulemodule.- import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////}
- フォームは、ルートコンポーネントのテンプレートで定義されたアプリケーションレイアウトに表示されます。 - src/app/app.component.html- <app-actor-form />- 初期テンプレートは、2つのフォームグループと送信ボタンを持つフォームのレイアウトを定義します。 フォームグループは、Actorデータモデルの2つのプロパティ(名前とスタジオ)に対応します。 各グループには、ラベルとユーザー入力を得るためのボックスがあります。 - 名前<input>コントロール要素には、HTML5のrequired属性があります
- スタジオ<input>コントロール要素には、studioはオプションであるため、属性がありません
 - Submitボタンには、スタイリングのためのいくつかのクラスがあります。 現時点では、フォームのレイアウトはすべてプレーンなHTML5で、バインディングやディレクティブはありません。 
- 名前
- サンプルフォームは、Twitter Bootstrapの - container、- form-group、- form-control、- btnといったスタイルクラスを使用しています。 これらのスタイルを使用するには、アプリケーションのスタイルシートでライブラリをインポートします。- src/styles.css- @import url('https://unpkg.com/[email protected]/dist/css/bootstrap.min.css');
- フォームでは、アクターのスキルを、 - ActorFormComponentで内部的に維持されている事前定義された- skillsリストから選択する必要があります。 The Angular- @forloop iterates over the data values to populate the- <select>element.- src/app/actor-form/actor-form.component.html (skills)- <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div>
アプリケーションを今すぐ実行すると、選択コントロールにスキルのリストが表示されます。 入力要素は、まだデータ値やイベントにバインドされていないため、空欄であり、動作しません。
入力コントロールをデータプロパティにバインドする
次のステップは、入力コントロールを対応するActorプロパティに双方向データバインディングでバインドすることです。これにより、ユーザー入力がデータモデルを更新し、データのプログラムによる変更がディスプレイを更新します。
FormsModuleで宣言されたngModelディレクティブを使用すると、テンプレート駆動型フォームのテンプレート内のコントロールを、データモデルのプロパティにバインドできます。
双方向データバインディングの構文[(ngModel)]="..."を使用してディレクティブを含めると、Angularはコントロールの値とユーザー操作を追跡し、ビューとモデルを同期させることができます。
- テンプレートファイルactor-form.component.htmlを編集します。
- 名前ラベルの隣の<input>タグを探します。
- ngModelディレクティブを追加し、双方向データバインディング構文- [(ngModel)]="..."を使用します。
src/app/actor-form/actor-form.component.html (excerpt)
<div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset              <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>HELPFUL: この例では、各入力タグの後に一時的な診断用補間{{model.name}}が追加され、対応するプロパティの現在のデータ値が表示されます。コメントは、双方向データバインディングの動作を観察し終わったら、診断用行を削除するよう思い出させてくれます。
フォーム全体のステータスにアクセスする
コンポーネントでFormsModuleをインポートすると、Angularはテンプレートの<form>タグにNgFormディレクティブを自動的に作成してアタッチします(NgFormのセレクターは<form>要素と一致するformだからです)。
NgFormとフォーム全体のステータスにアクセスするには、テンプレート参照変数を宣言します。
- テンプレートファイル - actor-form.component.htmlを編集します。
- テンプレート参照変数 - #actorFormを付けて- <form>タグを更新し、次の値を設定します。- src/app/actor-form/actor-form.component.html (excerpt)- <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div>- actorFormテンプレート変数は、フォーム全体を管理する- NgFormディレクティブインスタンスへの参照になります。
- アプリケーションを実行します。 
- 名前入力ボックスに入力し始めます。 - 文字を追加したり削除したりすると、データモデルにも表示および非表示されます。 
補間された値を表示する診断用行は、値が実際に入力ボックスからモデルに、そしてモデルから入力ボックスに戻っていることを示しています。
コントロール要素に名前を付ける
[(ngModel)]を要素に使用する場合は、その要素のname属性を定義する必要があります。
Angularは、割り当てられた名前を使用して、親<form>要素にアタッチされたNgFormディレクティブに要素を登録します。
例では、<input>要素にname属性を追加し、"name"に設定しました。これは、アクターの名前を表現するのに理にかなっています。
任意のユニークな値で構いませんが、わかりやすい名前を使用すると便利です。
- スタジオとスキルに同様の[(ngModel)]バインディングとname属性を追加します。
- 補間された値を表示する診断メッセージを削除できます。
- アクターモデル全体に双方向データバインディングが機能することを確認するために、コンポーネントのテンプレートの最上部に、jsonパイプを使用した新しいテキストバインディングを追加します。これは、データを文字列にシリアライズします。
これらの変更を加えた後、フォームテンプレートは次のようになります。
src/app/actor-form/actor-form.component.html (excerpt)
<div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset              <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>次のことに注意してください。
- 各 - <input>要素には- idプロパティがあります。 これは、- <label>要素の- for属性によって使用され、ラベルを入力コントロールに一致させます。 これは標準的なHTML機能です。
- 各 - <input>要素には、Angularがフォームにコントロールを登録するために使用する必要な- nameプロパティもあります。
効果を確認したら、{{ model | json }}テキストバインディングを削除できます。
フォームの状態を追跡する
Angularは、フォームが送信された後にform要素にng-submittedクラスを適用します。このクラスは、フォームが送信された後にフォームのスタイルを変更するために使用できます。
コントロールの状態を追跡する
コントロールにNgModelディレクティブを追加すると、コントロールの状態を表すクラス名がコントロールに追加されます。
これらのクラスは、コントロールの状態に基づいてコントロールのスタイルを変更するために使用できます。
次の表は、コントロールの状態に基づいてAngularが適用するクラス名を説明しています。
| 状態 | クラス(真の場合) | クラス(偽の場合) | 
|---|---|---|
| コントロールが訪問された。 | ng-touched | ng-untouched | 
| コントロールの値が変更された。 | ng-dirty | ng-pristine | 
| コントロールの値が有効。 | ng-valid | ng-invalid | 
Angularは、form要素にng-submittedクラスを適用しますが、
form要素内のコントロールには適用しません。
これらのCSSクラスを使用して、コントロールの状態に基づいてコントロールのスタイルを定義します。
コントロールの状態を観察する
フレームワークによるクラスの追加と削除を確認するには、ブラウザの開発者ツールを開き、アクターの名前を表す<input>要素を調べます。
- ブラウザの開発者ツールを使用して、Name入力ボックスに対応する - <input>要素を探します。 要素には、"form-control"に加えて、複数のCSSクラスがあることがわかります。
- 最初に表示すると、クラスは、値が有効であり、初期化またはリセット以降値が変更されていないこと、初期化またはリセット以降コントロールが訪問されていないことを示しています。 - <input class="form-control ng-untouched ng-pristine ng-valid">;
- Name - <input>ボックスで次のように操作し、表示されるクラスを観察します。- 見るだけで触れずにいます。 クラスは触られていないか、原始状態または有効であることを示しています。 
- 名前のボックスをクリックし、外をクリックします。 コントロールは訪問されたため、要素には - ng-untouchedクラスではなく- ng-touchedクラスがあります。
- 名前の最後にスラッシュを追加します。 これで触れられて、汚れています。 
- 名前を消去します。 これにより値が無効になるため、 - ng-invalidクラスが- ng-validクラスに置き換えられます。
 
状態の視覚的なフィードバックを作成する
ng-valid/ng-invalidペアは特に興味深いものです。
なぜなら、値が無効な場合に明確な視覚的な信号を送信したいからです。
また、必須フィールドをマークすることも必要です。
入力ボックスの左側に色の付いたバーを使用して、 必須フィールドと無効なデータを同時にマークできます。
この方法で外観を変更するには、次の手順を実行します。
- ng-*CSSクラスの定義を追加します。
- これらのクラス定義を新しいforms.cssファイルに追加します。
- 新しいファイルを、index.htmlと同じレベルにプロジェクトに追加します。
src/assets/forms.css
.ng-valid[required], .ng-valid.required  {  border-left: 5px solid #42A948; /* green */}.ng-invalid:not(form)  {  border-left: 5px solid #a94442; /* red */}- index.htmlファイルで、新しいスタイルシートを含めるように- <head>タグを更新します。
src/index.html (styles)
<!DOCTYPE html><html lang="en">  <head>    <title>Hero Form</title>    <base href="/">    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1">    <link rel="stylesheet"          href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css">    <link rel="stylesheet" href="assets/forms.css">  </head>  <body>    <app-root></app-root>  </body></html>検証エラーメッセージの表示と非表示
名前入力ボックスは必須であり、クリアするとバーが赤くなります。 これは何かが間違っていることを示していますが、ユーザーは何が間違っているのか、どうすればいいのかわかりません。 コントロールの状態をチェックして対応することで、役立つメッセージを提供できます。
スキル選択ボックスも必須ですが、この種のエラー処理は必要ありません。なぜなら、選択ボックスはすでに選択を有効な値に制限しているからです。
エラーメッセージを適切に定義して表示するには、次の手順を実行します。
- 
      
      
  入力へのローカル参照を追加テンプレートから入力ボックスのAngularコントロールにアクセスするために使用できるテンプレート参照変数を使用して、 inputタグを拡張します。例では、変数は#name="ngModel"です。テンプレート参照変数( #name)は"ngModel"に設定されています。なぜなら、それはNgModel.exportAsプロパティの値だからです。このプロパティは、Angularに参照変数をディレクティブにどのようにリンクするかを指示します。
- 
      
      
  エラーメッセージを追加適切なエラーメッセージを含む <div>を追加します。
- 
      
      
  エラーメッセージを条件付きにするnameコントロールのプロパティを、メッセージ<div>要素のhiddenプロパティにバインドすることで、エラーメッセージを表示または非表示にします。
- 
      
      
  名前への条件付きエラーメッセージを追加次の例のように、 name入力ボックスに条件付きエラーメッセージを追加します。src/app/actor-form/actor-form.component.html (excerpt)<div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div>
src/app/actor-form/actor-form.component.html (hidden-error-msg)
<div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset              <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>この例では、コントロールが有効あるいは_原始状態_のいずれかの場合にメッセージを非表示にします。
原始状態とは、ユーザーがこのフォームに表示されてから値を変更していないことを意味します。
pristine状態を無視すると、値が有効な場合にのみメッセージを非表示にします。
新しい空白のアクターか無効なアクターがこのコンポーネントに表示されると、何も操作する前にエラーメッセージがすぐに表示されます。
メッセージは、ユーザーが無効な変更を加えた場合にのみ表示されるようにしたいかもしれません。
コントロールがpristine状態の間にメッセージを非表示にすると、その目的を達成できます。
次のステップで新しいアクターをフォームに追加すると、この選択肢の重要性についてわかります。
新しいアクターを追加する
この演習では、モデルデータに追加することで、ネイティブHTMLボタンのクリックイベントに対応する方法を示します。 フォームユーザーが新しいアクターを追加できるようにするには、クリックイベントに対応するNew Actorボタンを追加します。
- テンプレートで、フォームの下部に"New Actor" - <button>要素を配置します。
- コンポーネントファイルで、アクターデータモデルにアクター作成メソッドを追加します。 - src/app/actor-form/actor-form.component.ts (New Actor method)- import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////}
- ボタンのクリックイベントを、アクター作成メソッド - newActor()にバインドします。- src/app/actor-form/actor-form.component.html (New Actor button)- <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div>
- アプリケーションを再度実行し、New Actorボタンをクリックします。 - フォームがクリアされ、入力ボックスの左側の_必須_バーが赤くなり、 - nameと- skillプロパティが無効であることを示しています。 エラーメッセージは非表示になっていることに注意してください。 これは、フォームが原始状態であるためです。まだ何も変更していません。
- 名前を入力し、New Actorを再度クリックします。 - 入力ボックスはもはや原始状態ではないため、 - 名前は必須ですというエラーメッセージが表示されます。 フォームは、New Actorをクリックする前に名前を入力したことを覚えています。
- フォームコントロールの原始状態を復元するには、 - newActor()メソッドを呼び出した後に、フォームの- reset()メソッドを呼び出してすべてのフラグを強制的にクリアします。- src/app/actor-form/actor-form.component.html (Reset the form)- <div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div>- これで、New Actorをクリックすると、フォームとそのコントロールフラグの両方がリセットされます。 
    ngSubmitでフォームを送信する
  
  ユーザーは、フォームに記入したら送信できる必要があります。
フォームの下部にあるSubmitボタンは、それ自体では何も実行しませんが、type="submit"であるため、フォームの送信イベントをトリガーします。
このイベントに対応するには、次の手順を実行します。
- 
      
      
  ngOnSubmitを監視するフォームの ngSubmitイベントプロパティを、アクターフォームコンポーネントのonSubmit()メソッドにバインドします。src/app/actor-form/actor-form.component.html (ngSubmit)<div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div>
- 
      
      
  disabledプロパティをバインドするテンプレート参照変数 #actorFormを使用して、Submitボタンを含むフォームにアクセスし、イベントバインディングを作成します。フォームの全体的な有効性を示すプロパティを、Submitボタンの disabledプロパティにバインドします。src/app/actor-form/actor-form.component.html (submit-button)<div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div>
- 
      
      
  アプリケーションを実行するボタンが有効になっていることに注意してください。まだ何も役に立つことはしていません。 
- 
      
      
  名前の値を削除するこれは"必須"ルールに違反するため、エラーメッセージが表示されます。また、Submitボタンが無効になることにも注意してください。 ボタンの有効状態をフォームの有効性に明示的にワイヤリングする必要はありませんでした。 テンプレート参照変数を拡張されたフォーム要素に定義してから、ボタンコントロールでその変数を参照すると、 FormsModuleがこれを自動的に行いました。
フォーム送信に対応する
フォーム送信への応答を表示するには、データ入力領域を非表示にし、代わりに別のものを表示できます。
- 
      
      
  フォームをラップするフォーム全体を <div>でラップし、そのhiddenプロパティをActorFormComponent.submittedプロパティにバインドします。src/app/actor-form/actor-form.component.html (excerpt)<div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div>メインフォームは、 submittedプロパティがフォームを送信するまでは偽であるため、最初から表示されます。これは、ActorFormComponentからの次のフラグメントで示されています。src/app/actor-form/actor-form.component.ts (submitted)import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-actor-form', templateUrl: './actor-form.component.html', imports: [FormsModule, JsonPipe],})export class ActorFormComponent { skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting']; model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions'); submitted = false; onSubmit() { this.submitted = true; } newActor() { this.model = new Actor(42, '', ''); } heroine(): Actor { const myActress = new Actor(42, 'Marilyn Monroe', 'Singing'); console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn" return myActress; } //////// NOT SHOWN IN DOCS //////// // Reveal in html: // Name via form.controls = {{showFormControls(actorForm)}} showFormControls(form: any) { return form && form.controls.name && form.controls.name.value; // Tom Cruise } /////////////////////////////}Submitボタンをクリックすると、 submittedフラグが真になり、フォームが消えます。
- 
      
      
  送信された状態を追加するフォームが送信された状態の間に別のものを表示するには、新しい <div>ラッパーの下に次のHTMLを追加します。src/app/actor-form/actor-form.component.html (excerpt)<div class="container"> <div [hidden]="submitted"> <h1>Actor Form</h1> <form (ngSubmit)="onSubmit()" #actorForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="studio">Studio Affiliation</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill" #skill="ngModel"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger"> skill is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">New Actor</button> <em>with</em> reset <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button> <em>without</em> reset <!-- NOT SHOWN IN DOCS --> <div> <hr> Name via form.controls = {{ showFormControls(actorForm) }} </div> <!-- - --> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Studio</div> <div class="col-xs-9">{{ model.studio }}</div> </div> <div class="row"> <div class="col-xs-3">Skill</div> <div class="col-xs-9">{{ model.skill }}</div> </div> <br> <button type="button" class="btn btn-primary" (click)="submitted=false"> Edit </button> </div></div><!-- ==================================================== --> <div> <form> <!-- ... all of the form ... --> </form> </div><!-- ==================================================== --><hr><style> .no-style .ng-valid { border-left: 1px solid #CCC} .no-style .ng-invalid { border-left: 1px solid #CCC}</style><div class="no-style" style="margin-left: 4px"> <div class="container"> <h1>Actor Form</h1> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required> @for(skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- ==================================================== --> <hr> <div class="container"> <h1>Actor Form</h1> <form #actorForm="ngForm"> {{ model | json }} <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> </div> <div class="form-group"> <label for="studio">Studio</label> <input type="text" class="form-control" id="studio" [(ngModel)]="model.studio" name="studio"> </div> <div class="form-group"> <label for="skill">Skill</label> <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill"> @for (skill of skills; track $index) { <option [value]="skill">{{ skill }}</option> } </select> </div> <button type="submit" class="btn btn-success">Submit</button> </form> </div> <!-- EXTRA MATERIAL FOR DOCUMENTATION --> <hr> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{ model.name}} <hr> <input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event"> TODO: remove this: {{ model.name}}</div>この <div>は、補間バインディングで読み取り専用の俳優を表示します。これは、コンポーネントが送信された状態の間にのみ表示されます。代替表示には、_編集_ボタンが含まれます。このボタンのクリックイベントは、 submittedフラグをクリアする式にバインドされています。
- 
      
      
  編集ボタンをテストする編集ボタンをクリックして、表示を編集可能なフォームに戻します。 
まとめ
このページで説明したAngularフォームは、データの修正や検証などをサポートするために、 次のフレームワーク機能を活用しています。
- Angular HTMLフォームテンプレート
- @Componentデコレーターを含むフォームコンポーネントクラス
- NgForm.ngSubmitイベントプロパティにバインドすることで、フォーム送信を処理する
- #actorFormや- #nameなどのテンプレート参照変数
- 双方向データバインディングのための[(ngModel)]構文
- 検証とフォーム要素の変更追跡のためのname属性の使用
- 入力コントロールの参照変数のvalidプロパティは、コントロールが有効であるか、エラーメッセージを表示する必要があるかを示します
- NgFormの有効性にバインドすることで、Submitボタンの有効状態を制御する
- 有効でないコントロールについてユーザーに視覚的なフィードバックを提供するカスタムCSSクラス
アプリケーションの最終バージョンを示すコードを次に示します。
actor-form/actor-form.component.ts
import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({  selector: 'app-actor-form',  templateUrl: './actor-form.component.html',  imports: [FormsModule, JsonPipe],})export class ActorFormComponent {  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');  submitted = false;  onSubmit() {    this.submitted = true;  }  newActor() {    this.model = new Actor(42, '', '');  }  heroine(): Actor {    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"    return myActress;  }  //////// NOT SHOWN IN DOCS ////////  // Reveal in html:  //   Name via form.controls = {{showFormControls(actorForm)}}  showFormControls(form: any) {    return form && form.controls.name && form.controls.name.value; // Tom Cruise  }  /////////////////////////////}actor-form/actor-form.component.html
<div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset              <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>actor.ts
export class Actor {  constructor(    public id: number,    public name: string,    public skill: string,    public studio?: string,  ) {}}app.component.html
<app-actor-form />app.component.ts
import {Component} from '@angular/core';import {ActorFormComponent} from './actor-form/actor-form.component';@Component({  selector: 'app-root',  templateUrl: './app.component.html',  imports: [ActorFormComponent],})export class AppComponent {}main.ts
import {bootstrapApplication} from '@angular/platform-browser';import {AppComponent} from './app/app.component';import {provideZoneChangeDetection} from '@angular/core';bootstrapApplication(AppComponent, {  providers: [provideZoneChangeDetection({eventCoalescing: true})],}).catch((err) => console.error(err));forms.css
.ng-valid[required], .ng-valid.required  {  border-left: 5px solid #42A948; /* green */}.ng-invalid:not(form)  {  border-left: 5px solid #a94442; /* red */}