آنلاین

ساخت یک فرم template-driven

این tutorial نشان می‌دهد چطور یک فرم template-driven بسازید. elementهای control در فرم به data propertyهایی bind شده‌اند که input validation دارند. input validation کمک می‌کند یکپارچگی داده حفظ شود و styling تجربه‌ی کاربر را بهتر کند.

template-driven forms از two-way data binding استفاده می‌کنند تا وقتی در template تغییری ایجاد می‌شود data model در component به‌روزرسانی شود و برعکس.

تقریبا هر نوع فرمی را می‌توانید با یک Angular template بسازید: فرم login، فرم contact و تقریبا هر فرم business. می‌توانید controlها را خلاقانه layout کنید و آن‌ها را به داده‌ی object model خود bind کنید. می‌توانید validation ruleها را مشخص کنید، validation errorها را نمایش دهید، به‌صورت شرطی input گرفتن از controlهای مشخص را مجاز کنید، بازخورد بصری داخلی را فعال کنید و کارهای بسیار بیشتر.

اهداف

این tutorial به شما یاد می‌دهد چطور کارهای زیر را انجام دهید:

  • ساخت یک Angular form با component و template
  • استفاده از ngModel برای ساخت two-way data binding جهت خواندن و نوشتن مقدارهای input-control
  • فراهم کردن بازخورد بصری با استفاده از CSS classهای خاصی که state کنترل‌ها را دنبال می‌کنند
  • نمایش validation errorها به کاربر و اجازه دادن شرطی به input گرفتن از form controlها بر اساس وضعیت فرم
  • به‌اشتراک‌گذاری اطلاعات بین HTML elementها با استفاده از template reference variables

ساخت یک فرم template-driven

template-driven forms به directiveهایی تکیه می‌کنند که در FormsModule تعریف شده‌اند.

Directivesجزئیات
NgModelتغییرات مقدار در form element متصل‌شده را با تغییرات data model هماهنگ می‌کند و به شما اجازه می‌دهد با input validation و error handling به input کاربر واکنش نشان دهید.
NgFormیک instance سطح‌بالا از FormGroup می‌سازد و آن را به یک element از نوع <form> bind می‌کند تا form value و validation status تجمیع‌شده را دنبال کند. به محض اینکه FormsModule را import کنید، این directive به‌صورت پیش‌فرض روی همه‌ی tagهای <form> فعال می‌شود. لازم نیست selector خاصی اضافه کنید.
NgModelGroupیک instance از FormGroup می‌سازد و آن را به یک DOM element bind می‌کند.

نمای کلی مرحله‌ها

در طول این tutorial، یک فرم نمونه را با مرحله‌های زیر به داده bind می‌کنید و input کاربر را مدیریت می‌کنید.

  • یک sample data model تعریف کنید.
  • زیرساخت لازم مثل FormsModule را اضافه کنید.
  • بررسی کنید ngModel چطور stateهای control را با CSS classها گزارش می‌کند.
  • برای controlها name بگذارید تا برای ngModel قابل دسترس باشند.
  • CSS سفارشی اضافه کنید تا درباره‌ی وضعیت، بازخورد بصری بدهد.
  • پیام‌های validation-error را نمایش یا پنهان کنید.
  • دکمه‌ی Submit را تا زمانی که فرم معتبر نیست disabled کنید.
  • بعد از submit، فرم کامل‌شده را با محتوای دیگری در صفحه جایگزین کنید.
  1. فرم پایه را بسازید.
  2. form controlها را با directive مربوط به ngModel و syntax مربوط به two-way data-binding به data propertyها bind کنید.
  3. اعتبار input و وضعیت control را با ngModel دنبال کنید.
  4. با اضافه کردن داده به model، به event بومی click روی button در HTML واکنش نشان دهید.
  5. submission فرم را با output property مربوط به ngSubmit در فرم مدیریت کنید.

ساخت فرم

  1. application نمونه‌ی ارائه‌شده کلاس Actor را می‌سازد که data model بازتاب‌یافته در فرم را تعریف می‌کند.
actor.ts
// #docregion
export class Actor {
  constructor(
    public id: number,
    public name: string,
    public skill: string,
    public studio?: string,
  ) {}
}
  1. layout و جزئیات فرم در کلاس ActorFormComponent تعریف می‌شوند.
actor-form.component.ts (v1)
// #docplaster
// #docregion , v1, final
import {Component} from '@angular/core';

import {Actor} from '../actor';
import {FormsModule} from '@angular/forms';
import {JsonPipe} from '@angular/common';

// #docregion imports
@Component({
  selector: 'app-actor-form',
  templateUrl: './actor-form.component.html',
  imports: [FormsModule, JsonPipe],
})
export class ActorFormComponent {
  // #enddocregion imports
  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];

  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');

  // #docregion submitted
  submitted = false;

  onSubmit() {
    this.submitted = true;
  }
  // #enddocregion submitted

  // #enddocregion final
  // #enddocregion v1

  // #docregion final, new-actor
  newActor() {
    this.model = new Actor(42, '', '');
  }
  // #enddocregion final, new-actor

  heroine(): Actor {
    // #docregion Marilyn
    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');
    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"
    // #enddocregion 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
  }

  /////////////////////////////

  // #docregion v1, final
}

مقدار selector در component، یعنی "app-actor-form"، یعنی می‌توانید این فرم را با tag <app-actor-form> داخل template والد قرار دهید.

  1. کد زیر یک instance جدید از actor می‌سازد تا فرم اولیه بتواند یک actor نمونه را نشان دهد.
ts
// #docplaster
// #docregion , v1, final
import {Component} from '@angular/core';

import {Actor} from '../actor';
import {FormsModule} from '@angular/forms';
import {JsonPipe} from '@angular/common';

// #docregion imports
@Component({
  selector: 'app-actor-form',
  templateUrl: './actor-form.component.html',
  imports: [FormsModule, JsonPipe],
})
export class ActorFormComponent {
  // #enddocregion imports
  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];

  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');

  // #docregion submitted
  submitted = false;

  onSubmit() {
    this.submitted = true;
  }
  // #enddocregion submitted

  // #enddocregion final
  // #enddocregion v1

  // #docregion final, new-actor
  newActor() {
    this.model = new Actor(42, '', '');
  }
  // #enddocregion final, new-actor

  heroine(): Actor {
    // #docregion Marilyn
    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');
    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"
    // #enddocregion 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
  }

  /////////////////////////////

  // #docregion v1, final
}

این demo از داده‌ی ساختگی برای model و skills استفاده می‌کند. در یک app واقعی، یک data service inject می‌کنید تا داده‌ی واقعی را بگیرید و ذخیره کنید، یا این propertyها را به‌عنوان input و output expose می‌کنید.

  1. component با import کردن module مربوط به FormsModule قابلیت Forms را فعال می‌کند.
ts
// #docplaster
// #docregion , v1, final
import {Component} from '@angular/core';

import {Actor} from '../actor';
import {FormsModule} from '@angular/forms';
import {JsonPipe} from '@angular/common';

// #docregion imports
@Component({
  selector: 'app-actor-form',
  templateUrl: './actor-form.component.html',
  imports: [FormsModule, JsonPipe],
})
export class ActorFormComponent {
  // #enddocregion imports
  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];

  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');

  // #docregion submitted
  submitted = false;

  onSubmit() {
    this.submitted = true;
  }
  // #enddocregion submitted

  // #enddocregion final
  // #enddocregion v1

  // #docregion final, new-actor
  newActor() {
    this.model = new Actor(42, '', '');
  }
  // #enddocregion final, new-actor

  heroine(): Actor {
    // #docregion Marilyn
    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');
    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"
    // #enddocregion 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
  }

  /////////////////////////////

  // #docregion v1, final
}
  1. فرم در application layoutی نمایش داده می‌شود که template مربوط به root component تعریف کرده است.
app.component.html
<app-actor-form />

template اولیه layout فرمی با دو form group و یک submit button را تعریف می‌کند. form groupها با دو property از data model مربوط به Actor متناظرند: name و studio. هر group یک label و یک box برای input کاربر دارد.

  • element مربوط به <input> برای Name attribute مربوط به HTML5 یعنی required را دارد.
  • element مربوط به <input> برای Studio این attribute را ندارد، چون studio اختیاری است.

دکمه‌ی Submit چند class برای styling دارد. در این نقطه، layout فرم کاملا HTML5 ساده است و binding یا directive ندارد.

  1. فرم نمونه از چند style class از Twitter Bootstrap استفاده می‌کند: container، form-group، form-control و btn. برای استفاده از این styleها، stylesheet برنامه library را import می‌کند.
styles.css
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
  1. فرم نیاز دارد skill مربوط به actor از فهرست از پیش تعریف‌شده‌ی skills انتخاب شود که داخل ActorFormComponent نگه‌داری می‌شود. loop مربوط به @for در Angular روی data valueها iterate می‌کند تا element مربوط به <select> را پر کند.
actor-form.component.html (skills)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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>

اگر همین حالا application را اجرا کنید، فهرست skillها را در selection control می‌بینید. input elementها هنوز به data value یا event bind نشده‌اند، پس هنوز خالی هستند و رفتاری ندارند.

Bind کردن input controlها به data propertyها

مرحله‌ی بعد این است که input controlها را با two-way data binding به propertyهای متناظر Actor bind کنید، تا در پاسخ به input کاربر data model را به‌روزرسانی کنند و در پاسخ به تغییرات programmatic در داده، display را هم به‌روزرسانی کنند.

directive مربوط به ngModel که در FormsModule تعریف شده اجازه می‌دهد controlهای template-driven form را به propertyهای data model خود bind کنید. وقتی directive را با syntax مربوط به two-way data binding یعنی [(ngModel)] اضافه می‌کنید، Angular می‌تواند مقدار و تعامل کاربر با control را دنبال کند و view را با model sync نگه دارد.

  1. فایل template یعنی actor-form.component.html را ویرایش کنید.
  2. tag مربوط به <input> کنار label Name را پیدا کنید.
  3. directive مربوط به ngModel را با syntax two-way data binding یعنی [(ngModel)]="..." اضافه کنید.
actor-form.component.html (excerpt)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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>

دسترسی به وضعیت کلی فرم

وقتی FormsModule را در component خود import کردید، Angular به‌صورت خودکار یک directive از نوع NgForm ساخت و به tag مربوط به <form> در template وصل کرد، چون NgForm selector به نام form دارد که با elementهای <form> match می‌شود.

برای دسترسی به NgForm و وضعیت کلی فرم، یک template reference variable تعریف کنید.

  1. فایل template یعنی actor-form.component.html را ویرایش کنید.
  2. tag مربوط به <form> را با یک template reference variable به نام #actorForm به‌روزرسانی کنید و مقدار آن را به شکل زیر تنظیم کنید.
actor-form.component.html (excerpt)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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>

متغیر template به نام actorForm حالا referenceای به instance directive مربوط به NgForm است که کل فرم را کنترل می‌کند.

  1. app را اجرا کنید.
  2. در input box مربوط به Name شروع به تایپ کنید.

وقتی character اضافه و حذف می‌کنید، می‌بینید که آن‌ها در data model ظاهر و ناپدید می‌شوند.

خط تشخیصی که مقدارهای interpolated را نشان می‌دهد ثابت می‌کند مقدارها واقعا از input box به model و دوباره برعکس جریان دارند.

نام‌گذاری control elementها

وقتی روی یک element از [(ngModel)] استفاده می‌کنید، باید attributeای به نام name برای آن element تعریف کنید. Angular از نام اختصاص‌داده‌شده برای register کردن element با directive مربوط به NgForm استفاده می‌کند که به element والد <form> وصل شده است.

مثال یک attribute به نام name به element مربوط به <input> اضافه کرد و آن را روی "name" گذاشت، که برای نام actor منطقی است. هر مقدار یکتایی کافی است، اما استفاده از نام توصیفی کمک‌کننده است.

  1. bindingهای مشابه [(ngModel)] و attributeهای name را به Studio و Skill اضافه کنید.
  2. حالا می‌توانید پیام‌های تشخیصی‌ای را که مقدارهای interpolated را نشان می‌دهند حذف کنید.
  3. برای تایید اینکه two-way data binding برای کل actor model کار می‌کند، یک text binding جدید با pipe مربوط به json در بالای template component اضافه کنید که داده را به string serialize می‌کند.

بعد از این اصلاحات، template فرم باید شبیه زیر باشد:

actor-form.component.html (excerpt)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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>

متوجه می‌شوید که:

  • هر element از نوع <input> یک property به نام id دارد. این property توسط attribute مربوط به for در element <label> استفاده می‌شود تا label با input control خودش match شود. این یک قابلیت استاندارد HTML است.
  • هر element از نوع <input> همچنین property موردنیاز name را دارد که Angular برای register کردن control با فرم از آن استفاده می‌کند.

وقتی اثرها را مشاهده کردید، می‌توانید text binding مربوط به {{ model | json }} را حذف کنید.

دنبال کردن stateهای فرم

Angular بعد از submit شدن فرم، class مربوط به ng-submitted را روی elementهای form اعمال می‌کند. از این class می‌توان برای تغییر style فرم بعد از submit استفاده کرد.

دنبال کردن stateهای control

اضافه کردن directive مربوط به NgModel به یک control، نام classهایی به آن control اضافه می‌کند که state آن را توصیف می‌کنند. از این classها می‌توان برای تغییر style کنترل بر اساس state آن استفاده کرد.

جدول زیر نام classهایی را توصیف می‌کند که Angular بر اساس state کنترل اعمال می‌کند.

StatesClass if trueClass if false
The control has been visited.ng-touchedng-untouched
The control's value has changed.ng-dirtyng-pristine
The control's value is valid.ng-validng-invalid

Angular هنگام submission، class مربوط به ng-submitted را هم روی elementهای form اعمال می‌کند، اما نه روی controlهای داخل element مربوط به form.

شما از این CSS classها برای تعریف styleهای control بر اساس status آن استفاده می‌کنید.

مشاهده‌ی stateهای control

برای دیدن اینکه framework چطور classها را اضافه و حذف می‌کند، developer tools مرورگر را باز کنید و element مربوط به <input> را که نام actor را نمایش می‌دهد inspect کنید.

  1. با developer tools مرورگر، element مربوط به <input> را که با input box مربوط به Name متناظر است پیدا کنید. می‌بینید که element علاوه بر "form-control" چند CSS class دیگر هم دارد.
  1. وقتی ابتدا آن را باز می‌کنید، classها نشان می‌دهند که مقدار معتبری دارد، مقدار از زمان initialization یا reset تغییر نکرده و control از زمان initialization یا reset بازدید نشده است.

```html

```

  • نگاه کنید اما دست نزنید. classها نشان می‌دهند untouched، pristine و valid است.
  1. کارهای زیر را روی box مربوط به <input> برای Name انجام دهید و ببینید کدام classها ظاهر می‌شوند.
  • داخل name box کلیک کنید، سپس بیرون آن کلیک کنید. control حالا بازدید شده است و element به‌جای class مربوط به ng-untouched، class مربوط به ng-touched دارد.
  • چند slash به انتهای name اضافه کنید. حالا touched و dirty است.
  • name را پاک کنید. این کار مقدار را invalid می‌کند، پس class مربوط به ng-invalid جایگزین ng-valid می‌شود.

ساخت بازخورد بصری برای stateها

جفت ng-valid/ng-invalid به‌خصوص جالب است، چون می‌خواهید وقتی مقدارها نامعتبر هستند یک signal بصری قوی بدهید. همچنین می‌خواهید fieldهای required را مشخص کنید.

می‌توانید fieldهای required و داده‌ی invalid را هم‌زمان با یک نوار رنگی در سمت چپ input box مشخص کنید.

برای تغییر ظاهر به این شکل، مرحله‌های زیر را انجام دهید.

  1. definitionهایی برای CSS classهای ng-* اضافه کنید.
  2. این class definitionها را به یک فایل جدید به نام forms.css اضافه کنید.
  3. فایل جدید را به پروژه اضافه کنید، به‌عنوان sibling فایل index.html:
forms.css
/* #docregion */
.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}
/* #enddocregion */
  1. در فایل index.html، tag مربوط به <head> را به‌روزرسانی کنید تا stylesheet جدید را شامل شود.
index.html (styles)
<!doctype html>
<!-- #docregion -->
<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/bootstrap@3.3.7/dist/css/bootstrap.min.css" />
    <!-- #docregion styles -->
    <link rel="stylesheet" href="assets/forms.css" />
    <!-- #enddocregion styles -->
  </head>

  <body>
    <app-root></app-root>
  </body>
</html>

نمایش و پنهان کردن پیام‌های validation error

input box مربوط به Name required است و پاک کردن آن نوار را قرمز می‌کند. این نشان می‌دهد چیزی اشتباه است، اما کاربر نمی‌داند مشکل چیست یا باید چه کند. می‌توانید با بررسی و واکنش به state کنترل، پیام مفیدی فراهم کنید.

select box مربوط به Skill هم required است، اما به این نوع error handling نیاز ندارد چون selection box خودش انتخاب را به مقدارهای معتبر محدود می‌کند.

برای تعریف و نمایش پیام خطا در زمان مناسب، مرحله‌های زیر را انجام دهید.

tag مربوط به input را با یک template reference variable گسترش دهید که بتوانید از داخل template برای دسترسی به Angular control مربوط به input box استفاده کنید. در مثال، این متغیر #name="ngModel" است.

template reference variable یعنی #name روی "ngModel" تنظیم شده، چون این مقدار property مربوط به NgModel.exportAs است. این property به Angular می‌گوید چطور یک reference variable را به یک directive link کند.

یک <div> اضافه کنید که پیام خطای مناسب را در خود دارد.

با bind کردن propertyهای control مربوط به name به property مربوط به hidden در element پیام <div>، پیام خطا را نمایش یا پنهان کنید.

actor-form.component.html (hidden-error-msg)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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 box مربوط به name اضافه کنید، مثل مثال زیر.

actor-form.component.html (excerpt)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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 جدید

این تمرین نشان می‌دهد چطور می‌توانید با اضافه کردن داده به model، به event بومی click روی button در HTML واکنش نشان دهید. برای اینکه کاربران فرم بتوانند actor جدید اضافه کنند، یک دکمه‌ی New Actor اضافه می‌کنید که به click event پاسخ می‌دهد.

  1. در template، یک element از نوع <button> با متن "New Actor" در پایین فرم قرار دهید.
  2. در فایل component، متد ساخت actor را به actor data model اضافه کنید.
actor-form.component.ts (New Actor method)
// #docplaster
// #docregion , v1, final
import {Component} from '@angular/core';

import {Actor} from '../actor';
import {FormsModule} from '@angular/forms';
import {JsonPipe} from '@angular/common';

// #docregion imports
@Component({
  selector: 'app-actor-form',
  templateUrl: './actor-form.component.html',
  imports: [FormsModule, JsonPipe],
})
export class ActorFormComponent {
  // #enddocregion imports
  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];

  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');

  // #docregion submitted
  submitted = false;

  onSubmit() {
    this.submitted = true;
  }
  // #enddocregion submitted

  // #enddocregion final
  // #enddocregion v1

  // #docregion final, new-actor
  newActor() {
    this.model = new Actor(42, '', '');
  }
  // #enddocregion final, new-actor

  heroine(): Actor {
    // #docregion Marilyn
    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');
    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"
    // #enddocregion 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
  }

  /////////////////////////////

  // #docregion v1, final
}
  1. event مربوط به click دکمه را به متد ساخت actor یعنی newActor() bind کنید.
actor-form.component.html (New Actor button)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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>
  1. دوباره application را اجرا کنید و روی دکمه‌ی New Actor کلیک کنید.

فرم پاک می‌شود و نوارهای required سمت چپ input box قرمز می‌شوند و نشان می‌دهند propertyهای name و skill نامعتبرند. توجه کنید پیام‌های خطا پنهان هستند. دلیلش این است که فرم pristine است؛ شما هنوز چیزی را تغییر نداده‌اید.

  1. یک name وارد کنید و دوباره روی New Actor کلیک کنید.

حالا application پیام خطای Name is required را نمایش می‌دهد، چون input box دیگر pristine نیست. فرم به خاطر دارد که قبل از کلیک روی New Actor یک name وارد کرده بودید.

  1. برای برگرداندن state مربوط به pristine در form controlها، بعد از فراخوانی متد newActor() همه‌ی flagها را به‌صورت imperative با فراخوانی متد reset() فرم پاک کنید.
actor-form.component.html (Reset the form)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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 هم فرم و هم flagهای controlهای آن را reset می‌کند.

Submit کردن فرم با ngSubmit

کاربر باید بتواند بعد از پر کردن این فرم، آن را submit کند. دکمه‌ی Submit در پایین فرم به‌تنهایی کاری انجام نمی‌دهد، اما به دلیل type آن، یعنی type="submit"، یک form-submit event را trigger می‌کند.

برای واکنش به این event، مرحله‌های زیر را انجام دهید.

event property مربوط به ngSubmit در فرم را به متد onSubmit() در actor-form component bind کنید.

actor-form.component.html (ngSubmit)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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>

از template reference variable یعنی #actorForm برای دسترسی به فرمی که دکمه‌ی Submit را در خود دارد استفاده کنید و یک event binding بسازید.

property فرم را که اعتبار کلی آن را نشان می‌دهد به property مربوط به disabled در دکمه‌ی Submit bind می‌کنید.

actor-form.component.html (submit-button)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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>

توجه کنید دکمه فعال است، هرچند هنوز کار مفیدی انجام نمی‌دهد.

این کار rule مربوط به "required" را نقض می‌کند، بنابراین پیام خطا را نمایش می‌دهد و توجه کنید که دکمه‌ی Submit را هم disabled می‌کند.

لازم نبود وضعیت enabled دکمه را به‌صورت صریح به اعتبار فرم wire کنید. FormsModule این کار را به‌صورت خودکار انجام داد، وقتی روی form element بهبودیافته یک template reference variable تعریف کردید و بعد در button control به همان متغیر ارجاع دادید.

واکنش به form submission

برای نشان دادن یک پاسخ به form submission، می‌توانید ناحیه‌ی ورود داده را پنهان کنید و چیز دیگری را به‌جای آن نمایش دهید.

کل فرم را داخل یک <div> wrap کنید و property مربوط به hidden آن را به property مربوط به ActorFormComponent.submitted bind کنید.

actor-form.component.html (excerpt)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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>

فرم اصلی از ابتدا visible است، چون property مربوط به submitted تا زمانی که فرم را submit نکرده‌اید false است؛ همان‌طور که این fragment از ActorFormComponent نشان می‌دهد:

actor-form.component.ts (submitted)
// #docplaster
// #docregion , v1, final
import {Component} from '@angular/core';

import {Actor} from '../actor';
import {FormsModule} from '@angular/forms';
import {JsonPipe} from '@angular/common';

// #docregion imports
@Component({
  selector: 'app-actor-form',
  templateUrl: './actor-form.component.html',
  imports: [FormsModule, JsonPipe],
})
export class ActorFormComponent {
  // #enddocregion imports
  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];

  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');

  // #docregion submitted
  submitted = false;

  onSubmit() {
    this.submitted = true;
  }
  // #enddocregion submitted

  // #enddocregion final
  // #enddocregion v1

  // #docregion final, new-actor
  newActor() {
    this.model = new Actor(42, '', '');
  }
  // #enddocregion final, new-actor

  heroine(): Actor {
    // #docregion Marilyn
    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');
    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"
    // #enddocregion 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
  }

  /////////////////////////////

  // #docregion v1, final
}

وقتی روی دکمه‌ی Submit کلیک می‌کنید، flag مربوط به submitted برابر true می‌شود و فرم ناپدید می‌شود.

برای نمایش چیز دیگری وقتی فرم در submitted state است، HTML زیر را پایین wrapper جدید <div> اضافه کنید.

actor-form.component.html (excerpt)
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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> که یک actor فقط‌خواندنی را با interpolation bindingها نمایش می‌دهد، فقط وقتی component در submitted state است ظاهر می‌شود.

نمای جایگزین شامل یک دکمه‌ی Edit است که click event آن به expressionای bind شده که flag مربوط به submitted را پاک می‌کند.

روی دکمه‌ی *Edit* کلیک کنید تا display دوباره به فرم قابل ویرایش برگردد.

خلاصه

فرم Angularی که در این صفحه بررسی شد از قابلیت‌های زیر در framework استفاده می‌کند تا از تغییر داده، validation و موارد دیگر پشتیبانی کند.

  • یک template فرم HTML در Angular
  • یک کلاس form component با decorator مربوط به @Component
  • مدیریت form submission با bind شدن به event property مربوط به NgForm.ngSubmit
  • template-reference variableهایی مثل #actorForm و #name
  • syntax مربوط به [(ngModel)] برای two-way data binding
  • استفاده از attributeهای name برای validation و دنبال کردن تغییر form-element
  • property مربوط به valid روی reference variable در input controlها نشان می‌دهد آیا control معتبر است یا باید پیام خطا نمایش دهد
  • کنترل وضعیت enabled دکمه‌ی Submit با bind شدن به اعتبار NgForm
  • CSS classهای سفارشی که درباره‌ی controlهای نامعتبر به کاربر بازخورد بصری می‌دهند

کد نسخه‌ی نهایی application اینجاست:

ts
// #docplaster
// #docregion , v1, final
import {Component} from '@angular/core';

import {Actor} from '../actor';
import {FormsModule} from '@angular/forms';
import {JsonPipe} from '@angular/common';

// #docregion imports
@Component({
  selector: 'app-actor-form',
  templateUrl: './actor-form.component.html',
  imports: [FormsModule, JsonPipe],
})
export class ActorFormComponent {
  // #enddocregion imports
  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];

  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');

  // #docregion submitted
  submitted = false;

  onSubmit() {
    this.submitted = true;
  }
  // #enddocregion submitted

  // #enddocregion final
  // #enddocregion v1

  // #docregion final, new-actor
  newActor() {
    this.model = new Actor(42, '', '');
  }
  // #enddocregion final, new-actor

  heroine(): Actor {
    // #docregion Marilyn
    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');
    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"
    // #enddocregion 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
  }

  /////////////////////////////

  // #docregion v1, final
}
html
<!-- #docplaster -->
<!-- #docregion final -->
<div class="container">
  <!-- #docregion edit-div -->
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <!-- #docregion ngSubmit -->
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <!-- #enddocregion ngSubmit, edit-div -->
      <div class="form-group">
        <!-- #docregion name-with-error-msg -->
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <!-- #docregion hidden-error-msg -->
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          <!-- #enddocregion hidden-error-msg -->
          Name is required
        </div>
        <!-- #enddocregion name-with-error-msg -->
      </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>

      <!-- #docregion submit-button -->
      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <!-- #enddocregion submit-button -->
      <!-- #docregion new-actor-button-form-reset -->
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
      <!-- #enddocregion new-actor-button-form-reset -->
      <!-- #enddocregion final -->
      <em>with</em> reset &nbsp;&nbsp;
      <!-- #docregion new-actor-button-no-reset -->
      <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
      <!-- #enddocregion new-actor-button-no-reset -->
      <em>without</em> reset

      <!-- NOT SHOWN IN DOCS -->
      <div>
        <hr />
        Name via form.controls = {{ showFormControls(actorForm) }}
      </div>
      <!-- - -->
      <!-- #docregion final -->
    </form>
  </div>

  <!-- #docregion submitted -->
  <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>
  <!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->

<!-- ====================================================  -->
<div>
  <form>
    <!-- #docregion edit-div -->

    <!-- ... all of the form ... -->
  </form>
</div>
<!-- #enddocregion edit-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>

      <!-- #docregion skills -->
      <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>
      <!-- #enddocregion skills -->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- ====================================================  -->
  <hr />
  <div class="container">
    <h1>Actor Form</h1>
    <!-- #docregion template-variable-->
    <form #actorForm="ngForm">
      <!-- #enddocregion template-variable-->
      <!-- #docregion ngModel-2-->
      {{ 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>

      <!-- #enddocregion ngModel-2-->
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
  </div>

  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
  <hr />
  <!-- #docregion ngModelName-1 -->
  <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}
  <!-- #enddocregion ngModelName-1 -->
  <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>
ts
// #docregion
export class Actor {
  constructor(
    public id: number,
    public name: string,
    public skill: string,
    public studio?: string,
  ) {}
}
html
<app-actor-form />
ts
// #docregion
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 {}
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));
css
/* #docregion */
.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}
/* #enddocregion */