填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

建構範本驅動表單

Building a template-driven form

本課程將為你示範如何建立一個範本驅動表單,它的控制元件元素繫結到資料屬性,並透過輸入驗證來保持資料的完整性和樣式,以改善使用者體驗。

This tutorial shows you how to create a template-driven form whose control elements are bound to data properties, with input validation to maintain data integrity and styling to improve the user experience.

當在範本中進行更改時,範本驅動表單會使用雙向資料繫結來更新元件中的資料模型,反之亦然。

Template-driven forms use two-way data binding to update the data model in the component as changes are made in the template and vice versa.

Angular 支援兩種互動式表單的設計方法。你可以使用 Angular 中的範本語法和指令,以及本課程中描述的表單專用指令和技巧編寫範本來建構表單,或者你可以使用響應式方式(或叫模型驅動方式)來建構表單。

Angular supports two design approaches for interactive forms. You can build forms by writing templates using Angular template syntax and directives with the form-specific directives and techniques described in this tutorial, or you can use a reactive (or model-driven) approach to build forms.

範本驅動表單適用於小型或簡單的表單,而響應式表單則更具延展性,適用於複雜表單。要比較這兩種方法,參閱“表單簡介”

Template-driven forms are suitable for small or simple forms, while reactive forms are more scalable and suitable for complex forms. For a comparison of the two approaches, see Introduction to Forms

你可以用 Angular 範本來建構各種表單,比如登入表單、聯絡人表單和幾乎所有的業務表單。你可以創造性地對控制元件進行佈局並把它們繫結到物件模型的資料上。你可以指定驗證規則並顯示驗證錯誤,有條不紊地啟用或禁用特定控制元件,觸發內建的視覺反饋等等。

You can build almost any kind of form with an Angular template—login forms, contact forms, and pretty much any business form. You can lay out the controls creatively and bind them to the data in your object model. You can specify validation rules and display validation errors, conditionally enable or disable specific controls, trigger built-in visual feedback, and much more.

本課程將向你展示如何透過一個簡化的範例表單來從頭建構一個表單,就像“英雄之旅”課程的中用一個表單來講解這些技巧一樣。

This tutorial shows you how to build a form from scratch, using a simplified sample form like the one from the Tour of Heroes tutorial to illustrate the techniques.

執行或下載範例應用:現場演練 / 下載範例

Run or download the example app:現場演練 / 下載範例.

目標

Objectives

本課程將教你如何執行以下操作:

This tutorial teaches you how to do the following:

  • 使用元件和範本建構一個 Angular 表單。

    Build an Angular form with a component and template.

  • 使用 ngModel 建立雙向資料繫結,以便讀寫輸入控制元件的值。

    Use ngModel to create two-way data bindings for reading and writing input-control values.

  • 使用追蹤控制元件狀態的特殊 CSS 類別來提供視覺反饋。

    Provide visual feedback using special CSS classes that track the state of the controls.

  • 向用戶顯示驗證錯誤,並根據表單狀態啟用或禁用表單控制元件。

    Display validation errors to users and enable or disable form controls based on the form status.

  • 使用範本參考變數在 HTML 元素之間共享資訊。

    Share information across HTML elements using template reference variables.

先決條件

Prerequisites

在進一步研究範本驅動表單之前,你應該對下列內容有一個基本的瞭解。

Before going further into template-driven forms, you should have a basic understanding of the following.

建構一個範本驅動表單

Build a template-driven form

範本驅動表單依賴於 FormsModule 定義的指令。

Template-driven forms rely on directives defined in the FormsModule.

  • NgModel 指令會協調其附著在的表單元素中的值變更與資料模型中的變更,以便你透過輸入驗證和錯誤處理來響應使用者輸入。

    The NgModel directive reconciles value changes in the attached form element with changes in the data model, allowing you to respond to user input with input validation and error handling.

  • NgForm 指令會建立一個最上層的 FormGroup 實例,並把它繫結到 <form> 元素上,以追蹤它所聚合的那些表單值並驗證狀態。只要你匯入了 FormsModule,預設情況下這個指令就會在所有 <form> 標籤上啟用。你不需要新增特殊的選擇器。

    The NgForm directive creates a top-level FormGroup instance and binds it to a <form> element to track aggregated form value and validation status. As soon as you import FormsModule, this directive becomes active by default on all <form> tags. You don't need to add a special selector.

  • NgModelGroup 指令會建立 FormGroup 的實例並把它繫結到 DOM 元素中。

    The NgModelGroup directive creates and binds a FormGroup instance to a DOM element.

範例應用

The sample application

英雄僱傭管理局使用本指南中的範例表單來維護英雄的個人資訊。畢竟英雄也要工作啊。這個表單有助於該機構將正確的英雄與正確的危機匹配起來。

The sample form in this guide is used by the Hero Employment Agency to maintain personal information about heroes. Every hero needs a job. This form helps the agency match the right hero with the right crisis.

該表單突出了一些易於使用的設計特性。比如,這兩個必填欄位的左邊是綠色條,以便讓它們醒目。這些欄位都有初始值,所以表單是有效的,並且 Submit 按鈕也是啟用的。

The form highlights some design features that make it easier to use. For instance, the two required fields have a green bar on the left to make them easy to spot. These fields have initial values, so the form is valid and the Submit button is enabled.

當你使用這個表單時,你將學習如何包含驗證邏輯,如何使用標準 CSS 自訂表示式,以及如何處理錯誤條件以確保輸入的有效性。例如,如果使用者刪除了英雄的名字,那麼表單就會失效。該應用會檢測已更改的狀態,並以醒目的樣式顯示驗證錯誤。此外,Submit 按鈕會被禁用,輸入控制元件左側的“必填”欄也會從綠色變為紅色。

As you work with this form, you will learn how to include validation logic, how to customize the presentation with standard CSS, and how to handle error conditions to ensure valid input. If the user deletes the hero name, for example, the form becomes invalid. The app detects the changed status, and displays a validation error in an attention-grabbing style. In addition, the Submit button is disabled, and the "required" bar to the left of the input control changes from green to red.

步驟概述

Step overview

在本課程中,你將使用以下步驟將一個範例表單繫結到資料並處理使用者輸入。

In the course of this tutorial, you bind a sample form to data and handle user input using the following steps.

  1. 建立基本表單。

    Build the basic form.

    • 定義一個範例資料模型。

      Define a sample data model.

    • 包括必需的基礎設施,比如 FormsModule

      Include required infrastructure such as the FormsModule.

  2. 使用 ngModel 指令和雙向資料繫結語法把表單控制元件繫結到資料屬性。

    Bind form controls to data properties using the ngModel directive and two-way data-binding syntax.

    • 檢查 ngModel 如何使用 CSS 類別報告控制元件狀態。

      Examine how ngModel reports control states using CSS classes.

    • 為控制元件命名,以便讓 ngModel 可以訪問它們。

      Name controls to make them accessible to ngModel.

  3. ngModel 追蹤輸入的有效性和控制元件的狀態。

    Track input validity and control status using ngModel.

    • 新增自訂 CSS 來根據狀態提供視覺化反饋。

      Add custom CSS to provide visual feedback on the status.

    • 顯示和隱藏驗證錯誤資訊。

      Show and hide validation-error messages.

  4. 透過新增到模型資料來響應原生 HTML 按鈕的單擊事件。

    Respond to a native HTML button-click event by adding to the model data.

  5. 使用表單的 ngSubmit輸出屬性來處理表單提交。

    Handle form submission using the ngSubmitoutput property of the form.

    • 在表單生效之前,先禁用 Submit 按鈕。

      Disable the Submit button until the form is valid.

    • 在提交完成後,把已完成的表單替換成頁面上不同的內容。

      After submit, swap out the finished form for different content on the page.

建立表單

Build the form

你可以根據這裡提供的程式碼從頭建立範例應用,也可以檢視現場演練 / 下載範例

You can recreate the sample application from the code provided here, or you can examine or download the現場演練 / 下載範例.

  1. 這裡提供的範例應用會建立一個 Hero 類別,用於定義表單中所反映的資料模型。

    The provided sample application creates the Hero class which defines the data model reflected in the form.

    export class Hero { constructor( public id: number, public name: string, public power: string, public alterEgo?: string ) { } }
    src/app/hero.ts
          
          export class Hero {
    
      constructor(
        public id: number,
        public name: string,
        public power: string,
        public alterEgo?: string
      ) {  }
    
    }
        
  2. 該表單的佈局和細節是在 HeroFormComponent 類別中定義的。

    The form layout and details are defined in the HeroFormComponent class.

    import { Component } from '@angular/core'; import { Hero } from '../hero'; @Component({ selector: 'app-hero-form', templateUrl: './hero-form.component.html', styleUrls: ['./hero-form.component.css'] }) export class HeroFormComponent { powers = ['Really Smart', 'Super Flexible', 'Super Hot', 'Weather Changer']; model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet'); submitted = false; onSubmit() { this.submitted = true; } // TODO: Remove this when we're done get diagnostic() { return JSON.stringify(this.model); } }
    src/app/hero-form/hero-form.component.ts (v1)
          
          import { Component } from '@angular/core';
    
    import { Hero } from '../hero';
    
    @Component({
      selector: 'app-hero-form',
      templateUrl: './hero-form.component.html',
      styleUrls: ['./hero-form.component.css']
    })
    export class HeroFormComponent {
    
      powers = ['Really Smart', 'Super Flexible',
                'Super Hot', 'Weather Changer'];
    
      model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');
    
      submitted = false;
    
      onSubmit() { this.submitted = true; }
    
      // TODO: Remove this when we're done
      get diagnostic() { return JSON.stringify(this.model); }
    }
        

    該元件的 selector 值為 “app-hero-form”,意味著你可以用 <app-hero-form> 標籤把這個表單放到父範本中。

    The component's selector value of "app-hero-form" means you can drop this form in a parent template using the <app-hero-form> tag.

  3. 下面的程式碼會建立一個新的 hero 實例,以便讓初始的表單顯示一個範例英雄。

    The following code creates a new hero instance, so that the initial form can show an example hero.

    const myHero = new Hero(42, 'SkyDog', 'Fetch any object at any distance', 'Leslie Rollover'); console.log('My hero is called ' + myHero.name); // "My hero is called SkyDog"
          
          const myHero =  new Hero(42, 'SkyDog',
                           'Fetch any object at any distance',
                           'Leslie Rollover');
    console.log('My hero is called ' + myHero.name); // "My hero is called SkyDog"
        

    這個示範使用虛擬資料來表達 modelpowers 。在真正的應用中,你會注入一個數據服務來獲取和儲存實際資料,或者把它們作為輸入屬性和輸出屬性進行公開。

    This demo uses dummy data for model and powers. In a real app, you would inject a data service to get and save real data, or expose these properties as inputs and outputs.

  4. 該應用啟用了表單功能,並註冊了已建立的表單元件。

    The application enables the Forms feature and registers the created form component.

    import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { HeroFormComponent } from './hero-form/hero-form.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, HeroFormComponent ], providers: [], bootstrap: [ AppComponent ] }) export class AppModule { }
    src/app/app.module.ts
          
          import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { FormsModule } from '@angular/forms';
    
    import { AppComponent } from './app.component';
    import { HeroFormComponent } from './hero-form/hero-form.component';
    
    @NgModule({
      imports: [
        BrowserModule,
        FormsModule
      ],
      declarations: [
        AppComponent,
        HeroFormComponent
      ],
      providers: [],
      bootstrap: [ AppComponent ]
    })
    export class AppModule { }
        
  5. 該表單顯示在根元件範本定義的應用佈局中。

    The form is displayed in the application layout defined by the root component's template.

    <app-hero-form></app-hero-form>
    src/app/app.component.html
          
          <app-hero-form></app-hero-form>
        

    初始範本定義了一個帶有兩個表單組和一個提交按鈕的表單佈局。表單組對應於 Hero 資料模型的兩個屬性:name 和 alterEgo。每個組都有一個標籤和一個使用者輸入框。

    The initial template defines the layout for a form with two form groups and a submit button. The form groups correspond to two properties of the Hero data model, name and alterEgo. Each group has a label and a box for user input.

    • Name <input> 控制元件元素中包含了 HTML5 的 required 屬性。

      The Name <input> control element has the HTML5 required attribute.

    • Alter Ego <input> 沒有控制元件元素,因為 alterEgo 是可選的。

      The Alter Ego <input> control element does not because alterEgo is optional.

    Submit 按鈕裡面有一些用於樣式化的類別。此時,表單佈局全都是純 HTML5,沒有繫結或指令。

    The Submit button has some classes on it for styling. At this point, the form layout is all plain HTML5, with no bindings or directives.

  6. 範例表單使用的是 Twitter Bootstrap 中的一些樣式類別: containerform-groupform-controlbtn 。要使用這些樣式,就要在該應用的樣式表中匯入該函式庫。

    The sample form uses some style classes from Twitter Bootstrap: container, form-group, form-control, and btn. To use these styles, the app's style sheet imports the library.

    @import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
    src/styles.css
          
          @import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
        
  7. 這份表單讓英雄申請人從管理局批准過的固定清單中選出一項超能力。預定義 powers 列表是資料模型的一部分,在 HeroFormComponent 內部維護。 Angular 的NgForOf 指令會遍歷這些資料值,以填充這個 <select> 元素。

    The form makes the hero applicant choose one superpower from a fixed list of agency-approved powers. The predefined list of powers is part of the data model, maintained internally in HeroFormComponent. The Angular NgForOf directive iterates over the data values to populate the <select> element.

    <div class="form-group"> <label for="power">Hero Power</label> <select class="form-control" id="power" required> <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option> </select> </div>
    src/app/hero-form/hero-form.component.html (powers)
          
          <div class="form-group">
      <label for="power">Hero Power</label>
      <select class="form-control" id="power" required>
        <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
      </select>
    </div>
        

如果你現在正在執行該應用,你會看到選擇控制元件中的超能力列表。由於尚未將這些 input 元素繫結到資料值或事件,因此它們仍然是空白的,沒有任何行為。

If you run the app right now, you see the list of powers in the selection control. The input elements are not yet bound to data values or events, so they are still blank and have no behavior.

把輸入控制元件繫結到資料屬性

Bind input controls to data properties

下一步是使用雙向資料繫結把輸入控制元件繫結到相應的 Hero 屬性,這樣它們就可以透過更新資料模型來響應使用者的輸入,並透過更新顯示來響應資料中的程式化變更。

The next step is to bind the input controls to the corresponding Hero properties with two-way data binding, so that they respond to user input by updating the data model, and also respond to programmatic changes in the data by updating the display.

ngModel 指令是由 FormsModule 宣告的,它能讓你把範本驅動表單中的控制元件繫結到資料模型中的屬性。當你使用雙向資料繫結的語法 [(ngModel)] 引入該指令時,Angular 就可以追蹤控制元件的值和使用者互動,並保持檢視與模型的同步。

The ngModel directive declared in the FormsModule lets you bind controls in your template-driven form to properties in your data model. When you include the directive using the syntax for two-way data binding, [(ngModel)], Angular can track the value and user interaction of the control and keep the view synced with the model.

  1. 編輯範本 hero-form.component.html

    Edit the template file hero-form.component.html.

  2. 找到 Name 標籤旁邊的 <input> 標記。

    Find the <input> tag next to the Name label.

  3. 使用雙向資料繫結語法 [(ngModel)]="..." 新增 ngModel 指令。

    Add the ngModel directive, using two-way data binding syntax [(ngModel)]="...".

<input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO: remove this: {{model.name}}
src/app/hero-form/hero-form.component.html (excerpt)
      
      <input type="text" class="form-control" id="name"
       required
       [(ngModel)]="model.name" name="name">
TODO: remove this: {{model.name}}
    

這個例子中在每個 input 標記後面都有一個臨時的診斷插值 {{model.name}},以顯示相應屬性的當前資料值。本提醒是為了讓你在觀察完這個雙向資料繫結後刪除這些診斷行。

This example has a temporary diagnostic interpolation after each input tag, {{model.name}}, to show the current data value of the corresponding property. The note reminds you to remove the diagnostic lines when you have finished observing the two-way data binding at work.

訪問表單的整體狀態

Access the overall form status

當你匯入了 FormsModule 時,Angular 會自動為範本中的 <form> 標籤建立並附加一個 NgForm 指令。(因為 NgForm 定義了一個能匹配 <form> 元素的選擇器 form)。

When you imported the FormsModule in your component, Angular automatically created and attached an NgForm directive to the <form> tag in the template (because NgForm has the selector form that matches <form> elements).

要訪問 NgForm 和表單的整體狀態,就要宣告一個範本參考變數

To get access to the NgForm and the overall form status, declare a template reference variable.

  1. 編輯範本 hero-form.component.html

    Edit the template file hero-form.component.html.

  2. <form> 標籤新增範本參考變數 #heroForm,並把它的值設定如下。

    Update the <form> tag with a template reference variable, #heroForm, and set its value as follows.

    <form #heroForm="ngForm">
    src/app/hero-form/hero-form.component.html (excerpt)
          
          <form #heroForm="ngForm">
        

    範本變數 heroForm 現在是對 NgForm 指令實例的參考,該指令實例管理整個表單。

    The heroForm template variable is now a reference to the NgForm directive instance that governs the form as a whole.

  3. 執行該應用。

    Run the app.

  4. 開始在 Name 輸入框中輸入。

    Start typing in the Name input box.

    在新增和刪除字元時,你可以看到它們從資料模型中出現和消失。例如:

    As you add and delete characters, you can see them appear and disappear from the data model. For example:

    用來顯示插值的診斷行證明了這些值確實從輸入框流向了模型,然後再返回。

    The diagnostic line that shows interpolated values demonstrates that values are really flowing from the input box to the model and back again.

為控制元件元素命名

Naming control elements

在元素上使用 [(ngModel)] 時,必須為該元素定義一個 name 屬性。Angular 會用這個指定的名字來把這個元素註冊到父 <form> 元素上的 NgForm 指令中。

When you use [(ngModel)] on an element, you must define a name attribute for that element. Angular uses the assigned name to register the element with the NgForm directive attached to the parent <form> element.

這個例子中為 <input> 元素添加了一個 name 屬性,並把它的值設定為 “name”,用來表示英雄的名字。任何唯一的值都可以用,但最好用描述性的名稱。

The example added a name attribute to the <input> element and set it to "name", which makes sense for the hero's name. Any unique value will do, but using a descriptive name is helpful.

  1. Alter EgoHero Power新增類似的 [(ngModel)] 繫結和 name 屬性。

    Add similar [(ngModel)] bindings and name attributes to Alter Ego and Hero Power.

  2. 你現在可以移除顯示插值的診斷訊息了。

    You can now remove the diagnostic messages that show interpolated values.

  3. 要想確認雙向資料繫結是否在整個英雄模型上都有效,就要在該元件的頂部新增一個對 diagnostic 屬性的新繫結。

    To confirm that two-way data binding works for the entire hero model, add a new binding at the top to the component's diagnostic property.

表單範本修改完畢後,應如下所示:

After these revisions, the form template should look like the following:

{{diagnostic}} <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="alterEgo">Alter Ego</label> <input type="text" class="form-control" id="alterEgo" [(ngModel)]="model.alterEgo" name="alterEgo"> </div> <div class="form-group"> <label for="power">Hero Power</label> <select class="form-control" id="power" required [(ngModel)]="model.power" name="power"> <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option> </select> </div>
src/app/hero-form/hero-form.component.html (excerpt)
      
      {{diagnostic}}
<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="alterEgo">Alter Ego</label>
  <input type="text"  class="form-control" id="alterEgo"
         [(ngModel)]="model.alterEgo" name="alterEgo">
</div>

<div class="form-group">
  <label for="power">Hero Power</label>
  <select class="form-control"  id="power"
          required
          [(ngModel)]="model.power" name="power">
    <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
  </select>
</div>
    
  • 注意,每個 <input> 元素都有一個 id 屬性。<label> 元素的 for 屬性用它來把標籤匹配到輸入控制元件。這是一個標準的 HTML 特性

    Notice that each <input> element has an id property. This is used by the <label> element's for attribute to match the label to its input control. This is a standard HTML feature.

  • 每個 <input> 元素都有一個必需的 name 屬性,Angular 用它來登錄檔單中的控制元件。

    Each <input> element also has the required name property that Angular uses to register the control with the form.

如果你現在執行該應用並更改英雄模型的每個屬性,該表單可能會顯示如下:

If you run the app now and change every hero model property, the form might display like this:

透過表單頂部的診斷行可以確認所有的更改都已反映在模型中。

The diagnostic near the top of the form confirms that all of your changes are reflected in the model.

  1. 你已經觀察到了這種效果,可以刪除 {{diagnostic}} 綁定了。

    When you have observed the effects, you can delete the {{diagnostic}} binding.

追蹤控制元件狀態

Track control states

控制元件上的 NgModel 指令會追蹤該控制元件的狀態。它會告訴你使用者是否接觸過該控制元件、該值是否發生了變化,或者該值是否無效。 Angular 在控制元件元素上設定了特殊的 CSS 類別來反映其狀態,如下表所示。

The NgModel directive on a control tracks the state of that control. It tells you if the user touched the control, if the value changed, or if the value became invalid. Angular sets special CSS classes on the control element to reflect the state, as shown in the following table.

狀態

State

為 true 時的類別名稱

Class if true

為 false 時的類別名稱

Class if false

該控制元件已經被訪問過。

The control has been visited.

ng-touched ng-untouched

該控制元件的值已變化。

The control's value has changed.

ng-dirty ng-pristine

該控制元件的值是無效的。

The control's value is valid.

ng-valid ng-invalid

你可以用這些 CSS 類別來根據控制元件的狀態定義其樣式。

You use these CSS classes to define the styles for your control based on its status.

觀察控制元件狀態

Observe control states

要想知道框架是如何新增和移除這些類別的,請開啟瀏覽器的開發者工具,檢查代表英雄名字的 <input>

To see how the classes are added and removed by the framework, open the browser's developer tools and inspect the <input> element that represents the hero name.

  1. 使用瀏覽器的開發者工具,找到與 “Name” 輸入框對應的 <input> 元素。除了 “form-control” 類別之外,你還可以看到該元素有多個 CSS 類別。

    Using your browser's developer tools, find the <input> element that corresponds to the Name input box. You can see that the element has multiple CSS classes in addition to "form-control".

  2. 當你第一次啟動它的時候,這些類別表明它是一個有效的值,該值在初始化或重置之後還沒有改變過,並且在該控制元件自初始化或重置後也沒有被訪問過。

    When you first bring it up, the classes indicate that it has a valid value, that the value has not been changed since initialization or reset, and that the control has not been visited since initialization or reset.

    <input ... class="form-control ng-untouched ng-pristine ng-valid" ...>
          
          <input ... class="form-control ng-untouched ng-pristine ng-valid" ...>
        
  3. 在 Name <input> 框中執行以下操作,看看會出現哪些類別。

    Take the following actions on the Name <input> box, and observe which classes appear.

    • 檢視,但不要碰它。這些類別表明它沒有被碰過、還是最初的值,並且有效。

      Look but don't touch. The classes indicate that it is untouched, pristine, and valid.

    • Name 框內單擊,然後單擊它外部。該控制元件現在已被訪問過,該元素具有 ng-touched 類別,取代了 ng-untouched 類別。

      Click inside the name box, then click outside it. The control has now been visited, and the element has the ng-touched class instead of the ng-untouched class.

    • 在名字的末尾新增斜槓。現在它被碰過,而且是髒的(變化過)。

      Add slashes to the end of the name. It is now touched and dirty.

    • 刪掉這個名字。這會使該值無效,所以 ng-invalid 類別會取代 ng-valid 類別。

      Erase the name. This makes the value invalid, so the ng-invalid class replaces the ng-valid class.

為狀態建立視覺反饋

Create visual feedback for states

注意 ng-valid / ng-invalid 這兩個類別,因為你想在值無效時發出強烈的視覺訊號。你還要標記必填欄位。

The ng-valid/ng-invalid pair is particularly interesting, because you want to send a strong visual signal when the values are invalid. You also want to mark required fields.

你可以在輸入框的左側用彩條標記必填欄位和無效資料:

You can mark required fields and invalid data at the same time with a colored bar on the left of the input box:

要想用這種方式修改外觀,請執行以下步驟。

To change the appearance in this way, take the following steps.

  1. ng-* CSS 類別新增一些定義。

    Add definitions for the ng-* CSS classes.

  2. 把這些類別定義新增到一個新的 forms.css 檔案中。

    Add these class definitions to a new forms.css file.

  3. 把這個新檔案新增到專案中,作為 index.html 的兄弟:

    Add the new file to the project as a sibling to index.html:

    .ng-valid[required], .ng-valid.required { border-left: 5px solid #42A948; /* green */ } .ng-invalid:not(form) { border-left: 5px solid #a94442; /* red */ }
    src/assets/forms.css
          
          .ng-valid[required], .ng-valid.required  {
      border-left: 5px solid #42A948; /* green */
    }
    
    .ng-invalid:not(form)  {
      border-left: 5px solid #a94442; /* red */
    }
        
  4. index.html 檔案中,更新 <head> 標籤以包含新的樣式表。

    In the index.html file, update the <head> tag to include the new style sheet.

    <link rel="stylesheet" href="assets/forms.css">
    src/index.html (styles)
          
          <link rel="stylesheet" href="assets/forms.css">
        

顯示和隱藏驗證錯誤資訊

Show and hide validation error messages

Name 輸入框是必填的,清除它就會把彩條變成紅色。這表明有些東西是錯的,但是使用者並不知道要怎麼做或該做什麼。你可以透過檢視和響應控制元件的狀態來提供有用的資訊。

The Name input box is required and clearing it turns the bar red. That indicates that something is wrong, but the user doesn't know what is wrong or what to do about it. You can provide a helpful message by checking for and responding to the control's state.

當用戶刪除該名字時,該表單應如下所示:

When the user deletes the name, the form should look like this:

Hero Power 選擇框也是必填的,但它不需要這樣的錯誤處理,因為選擇框已經把選擇限制在有效值範圍內。

The Hero Power select box is also required, but it doesn't need this kind of error handling because the selection box already constrains the selection to valid values.

要在適當的時候定義和顯示錯誤資訊,請執行以下步驟。

To define and show an error message when appropriate, take the following steps.

  1. 使用範本參考變數擴充套件 <input> 標籤,你可以用來從範本中訪問輸入框的 Angular 控制元件。在這個例子中,該變數是 #name="ngModel"

    Extend the <input> tag with a template reference variable that you can use to access the input box's Angular control from within the template. In the example, the variable is #name="ngModel".

    範本參考變數( #name )設定為 "ngModel",因為 "ngModel" 是 NgModel.exportAs屬性的值。這個屬性告訴 Angular 如何把參考變數和指令連結起來。

    The template reference variable (#name) is set to "ngModel" because that is the value of the NgModel.exportAsproperty. This property tells Angular how to link a reference variable to a directive.

  2. 新增一個包含合適錯誤資訊 <div>

    Add a <div> that contains a suitable error message.

  3. 透過把 name 控制元件的屬性繫結到 <div> 元素的 hidden 屬性來顯示或隱藏錯誤資訊。

    Show or hide the error message by binding properties of the name control to the message <div> element's hidden property.

    <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
    src/app/hero-form/hero-form.component.html (hidden-error-msg)
          
          <div [hidden]="name.valid || name.pristine"
         class="alert alert-danger">
        

  4. name 輸入框新增一個有條件的錯誤資訊,如下例所示。

    Add a conditional error message to the name input box, as in the following example.

    <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div>
    src/app/hero-form/hero-form.component.html (excerpt)
          
          <label for="name">Name</label>
    <input type="text" class="form-control" id="name"
           required
           [(ngModel)]="model.name" name="name"
           #name="ngModel">
    <div [hidden]="name.valid || name.pristine"
         class="alert alert-danger">
      Name is required
    </div>
        
關於 "pristine"(原始)狀態的說明
Illustrating the "pristine" state

在這個例子中,當控制元件是有效的(valid)或者是原始的(pristine)時,你會隱藏這些訊息。 原始表示該使用者在此表單中顯示的值尚未更改過。如果你忽略了 pristine 狀態,那麼只有當值有效時才會隱藏這些訊息。如果你把一個新的(空白)英雄或一個無效的英雄傳給這個元件,你會立刻看到錯誤資訊,而這時候你還沒有做過任何事情。

In this example, you hide the message when the control is either valid or pristine. Pristine means the user hasn't changed the value since it was displayed in this form. If you ignore the pristine state, you would hide the message only when the value is valid. If you arrive in this component with a new (blank) hero or an invalid hero, you'll see the error message immediately, before you've done anything.

你可能希望只有在使用者做出無效更改時,才顯示該訊息。 因此當 pristine 狀態時,隱藏這條訊息就可以滿足這個目標。當你在下一步中為表單新增一個新的英雄時,就會看到這個選擇有多重要。

You might want the message to display only when the user makes an invalid change. Hiding the message while the control is in the pristine state achieves that goal. You'll see the significance of this choice when you add a new hero to the form in the next step.

新增一個新英雄

Add a new hero

本練習透過新增模型資料,展示了如何響應原生 HTML 按鈕單擊事件。要讓表單使用者新增一個新的英雄,就要新增一個能響應 click 事件的 New Hero 按鈕。

This exercise shows how you can respond to a native HTML button-click event by adding to the model data. To let form users add a new hero, you will add a New Hero button that responds to a click event.

  1. 在範本中,把 “New Hero” 這個 <button> 元素放在表單底部。

    In the template, place a "New Hero" <button> element at the bottom of the form.

  2. 在元件檔案中,把建立英雄的方法新增到英雄資料模型中。

    In the component file, add the hero-creation method to the hero data model.

    newHero() { this.model = new Hero(42, '', ''); }
    src/app/hero-form/hero-form.component.ts (New Hero method)
          
          newHero() {
      this.model = new Hero(42, '', '');
    }
        
  3. 把按鈕的 click 事件繫結到一個建立英雄的方法 newHero() 上。

    Bind the button's click event to a hero-creation method, newHero().

    <button type="button" class="btn btn-default" (click)="newHero()">New Hero</button>
    src/app/hero-form/hero-form.component.html (New Hero button)
          
          <button type="button" class="btn btn-default" (click)="newHero()">New Hero</button>
        
  4. 再次執行該應用,單擊 New Hero 按鈕。

    Run the application again and click the New Hero button.

    表單會清空,輸入框左側的必填欄會顯示紅色,說明 namepower 屬性無效。請注意,錯誤訊息是隱藏的。這是因為表單處於原始狀態。你還沒有改過任何東西。

    The form clears, and the required bars to the left of the input box are red, indicating invalid name and power properties. Notice that the error messages are hidden. This is because the form is pristine; you haven't changed anything yet.

  5. 輸入一個名字,然後再次點選 New Hero

    Enter a name and click New Hero again.

    現在,該應用會顯示一條錯誤資訊 Name is required,因為該輸入框不再是原始狀態。表單會記住你在單擊 New Hero 之前輸入過一個名字。

    Now the app displays a Name is required error message, because the input box is no longer pristine. The form remembers that you entered a name before clicking New Hero.

  6. 要恢復表單控制元件的原始狀態,可以在呼叫 newHero() 方法之後強制呼叫表單的 reset() 方法以清除所有標誌。

    To restore the pristine state of the form controls, clear all of the flags imperatively by calling the form's reset() method after calling the newHero() method.

    <button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button>
    src/app/hero-form/hero-form.component.html (Reset the form)
          
          <button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button>
        

    現在單擊 New Hero 會重置表單及其控制元件標誌。

    Now clicking New Hero resets both the form and its control flags.

關於使用事件繫結監聽 DOM 事件和更新相應元件屬性的更多資訊,請參閱“使用者輸入”指南。

See the User Input guide for more information about listening for DOM events with an event binding and updating a corresponding component property.

使用 ngSubmit 提交表單

Submit the form with ngSubmit

使用者應該可以在填寫之後提交這個表單。表單底部的 Submit 按鈕本身沒有任何作用,但由於它的型別(type="submit"),它會觸發一個表單提交事件。要響應此事件,請執行以下步驟。

The user should be able to submit this form after filling it in. The Submit button at the bottom of the form does nothing on its own, but it does trigger a form-submit event because of its type (type="submit"). To respond to this event, take the following steps.

  1. 把表單的 ngSubmit事件屬性繫結到一個 hero-form 元件的 onSubmit() 方法中。

    Bind the form's ngSubmitevent property to the hero-form component's onSubmit() method.

    <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
    src/app/hero-form/hero-form.component.html (ngSubmit)
          
          <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
        
  2. 使用範本參考變數 #heroForm 訪問包含 Submit 按鈕的表單,並建立一個事件繫結。你可以把表示它整體有效性的 form 屬性繫結到 Submit 按鈕的 disabled 屬性上。

    Use the template reference variable, #heroForm to access the form that contains the Submit button and create an event binding. You will bind the form property that indicates its overall validity to the Submit button's disabled property.

    <button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>
    src/app/hero-form/hero-form.component.html (submit-button)
          
          <button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>
        

  3. 立即執行該應用。注意,該按鈕已啟用 - 雖然它還沒有做任何有用的事情。

    Run the application now. Notice that the button is enabled—although it doesn't do anything useful yet.

你不必把按鈕的啟用狀態明確地關聯表單的有效性上。當 FormsModule 在增強的表單元素上定義範本參考變數時,會自動執行此操作,然後在按鈕控制元件中參考該變數。

You didn't have to explicitly wire the button's enabled state to the form's validity. The FormsModule did this automatically when you defined a template reference variable on the enhanced form element, then referred to that variable in the button control.

響應表單提交

Respond to form submission

要展示對表單提交的響應,你可以隱藏資料輸入區域並就地顯示其它內容。

To show a response to form submission, you can hide the data entry area and display something else in its place.

  1. 把整個表單包裹進一個 <div> 中並把它的 hidden 屬性繫結到 HeroFormComponent.submitted 屬性上。

    Wrap the entire form in a <div> and bind its hidden property to the HeroFormComponent.submitted property.

    <div [hidden]="submitted"> <h1>Hero Form</h1> <form (ngSubmit)="onSubmit()" #heroForm="ngForm"> <!-- ... all of the form ... --> </form> </div>
    src/app/hero-form/hero-form.component.html (excerpt)
          
          <div [hidden]="submitted">
      <h1>Hero Form</h1>
      <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
    
         <!-- ... all of the form ... -->
    
      </form>
    </div>
        

    • 主表單從一開始就是可見的,因為在提交之前,它的 submitted 屬性都是 false,正如 HeroFormComponent 中的這個片段所顯示的:

      The main form is visible from the start because the submitted property is false until you submit the form, as this fragment from the HeroFormComponent shows:

      submitted = false; onSubmit() { this.submitted = true; }
      src/app/hero-form/hero-form.component.ts (submitted)
            
            submitted = false;
      
      onSubmit() { this.submitted = true; }
          

    • 點選 Submit 按鈕後, submitted 標誌就變為 true,表單就會消失。

      When you click the Submit button, the submitted flag becomes true and the form disappears.

  2. 要在表單處於已提交狀態時顯示其它內容,請在新的 <div> 包裝器下新增以下 HTML。

    To show something else while the form is in the submitted state, add the following HTML below the new <div> wrapper.

    <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">Alter Ego</div> <div class="col-xs-9">{{ model.alterEgo }}</div> </div> <div class="row"> <div class="col-xs-3">Power</div> <div class="col-xs-9">{{ model.power }}</div> </div> <br> <button class="btn btn-primary" (click)="submitted=false">Edit</button> </div>
    src/app/hero-form/hero-form.component.html (excerpt)
          
          <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">Alter Ego</div>
        <div class="col-xs-9">{{ model.alterEgo }}</div>
      </div>
      <div class="row">
        <div class="col-xs-3">Power</div>
        <div class="col-xs-9">{{ model.power }}</div>
      </div>
      <br>
      <button class="btn btn-primary" (click)="submitted=false">Edit</button>
    </div>
        

    這個 <div> (用於顯示帶插值繫結的唯讀英雄)只在元件處於已提交狀態時才會出現。

    This <div>, which shows a read-only hero with interpolation bindings, appears only while the component is in the submitted state.

    另外還顯示了一個 Edit 按鈕,它的 click 事件繫結到了一個清除 submitted 標誌的表示式。

    The alternative display includes an Edit button whose click event is bound to an expression that clears the submitted flag.

  3. 單擊 Edit 按鈕,將顯示切換回可編輯的表單。

    Click the Edit button to switch the display back to the editable form.

總結

Summary

本頁討論的 Angular 表單利用了下列框架特性來支援資料修改,驗證等工作。

The Angular form discussed in this page takes advantage of the following framework features to provide support for data modification, validation, and more.

  • 一個 Angular HTML 表單範本。

    An Angular HTML form template.

  • @Component 裝飾器的表單元件類別。

    A form component class with a @Component decorator.

  • 繫結到 NgForm.ngSubmit 事件屬性來處理表單提交。

    Handling form submission by binding to the NgForm.ngSubmit event property.

  • 範本參考變數,比如 #heroForm#name

    Template-reference variables such as #heroForm and #name.

  • 雙向資料繫結的 [(ngModel)] 語法。

    [(ngModel)] syntax for two-way data binding.

  • name 屬性的用途是驗證和表單元素的變更追蹤。

    The use of name attributes for validation and form-element change tracking.

  • 用輸入控制元件上的參考變數的 valid 屬性來檢查控制元件是否有效,並據此顯示或隱藏錯誤資訊。

    The reference variable’s valid property on input controls to check if a control is valid and show or hide error messages.

  • NgForm 的有效性來控制 Submit 按鈕的啟用狀態。

    Controlling the Submit button's enabled state by binding to NgForm validity.

  • 自訂 CSS 類別,為使用者提供關於無效控制元件的視覺反饋。

    Custom CSS classes that provide visual feedback to users about invalid controls.

這裡是該應用最終版本的程式碼:

Here’s the code for the final version of the application:

import { Component } from '@angular/core'; import { Hero } from '../hero'; @Component({ selector: 'app-hero-form', templateUrl: './hero-form.component.html', styleUrls: ['./hero-form.component.css'] }) export class HeroFormComponent { powers = ['Really Smart', 'Super Flexible', 'Super Hot', 'Weather Changer']; model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet'); submitted = false; onSubmit() { this.submitted = true; } newHero() { this.model = new Hero(42, '', ''); } }<div class="container"> <div [hidden]="submitted"> <h1>Hero Form</h1> <form (ngSubmit)="onSubmit()" #heroForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> Name is required </div> </div> <div class="form-group"> <label for="alterEgo">Alter Ego</label> <input type="text" class="form-control" id="alterEgo" [(ngModel)]="model.alterEgo" name="alterEgo"> </div> <div class="form-group"> <label for="power">Hero Power</label> <select class="form-control" id="power" required [(ngModel)]="model.power" name="power" #power="ngModel"> <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option> </select> <div [hidden]="power.valid || power.pristine" class="alert alert-danger"> Power is required </div> </div> <button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button> <button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button> </form> </div> <div [hidden]="!submitted"> <h2>You submitted the following:</h2> <div class="row"> <div class="col-xs-3">Name</div> <div class="col-xs-9">{{ model.name }}</div> </div> <div class="row"> <div class="col-xs-3">Alter Ego</div> <div class="col-xs-9">{{ model.alterEgo }}</div> </div> <div class="row"> <div class="col-xs-3">Power</div> <div class="col-xs-9">{{ model.power }}</div> </div> <br> <button class="btn btn-primary" (click)="submitted=false">Edit</button> </div> </div>export class Hero { constructor( public id: number, public name: string, public power: string, public alterEgo?: string ) { } }import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { HeroFormComponent } from './hero-form/hero-form.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, HeroFormComponent ], providers: [], bootstrap: [ AppComponent ] }) export class AppModule { }<app-hero-form></app-hero-form>import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { }import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule);.ng-valid[required], .ng-valid.required { border-left: 5px solid #42A948; /* green */ } .ng-invalid:not(form) { border-left: 5px solid #a94442; /* red */ }
      
      import { Component } from '@angular/core';

import { Hero } from '../hero';

@Component({
  selector: 'app-hero-form',
  templateUrl: './hero-form.component.html',
  styleUrls: ['./hero-form.component.css']
})
export class HeroFormComponent {

  powers = ['Really Smart', 'Super Flexible',
            'Super Hot', 'Weather Changer'];

  model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');

  submitted = false;

  onSubmit() { this.submitted = true; }

  newHero() {
    this.model = new Hero(42, '', '');
  }
}