Angular 構(gòu)建模板驅(qū)動(dòng)表單

2022-07-20 10:13 更新

構(gòu)建模板驅(qū)動(dòng)表單

本教程將為你演示如何創(chuàng)建一個(gè)模板驅(qū)動(dòng)表單,它的控件元素綁定到數(shù)據(jù)屬性,并通過(guò)輸入驗(yàn)證來(lái)保持?jǐn)?shù)據(jù)的完整性和樣式,以改善用戶體驗(yàn)。

當(dāng)在模板中進(jìn)行更改時(shí),模板驅(qū)動(dòng)表單會(huì)使用雙向數(shù)據(jù)綁定來(lái)更新組件中的數(shù)據(jù)模型,反之亦然。

Angular 支持兩種交互式表單的設(shè)計(jì)方法。你可以使用 Angular 中的模板語(yǔ)法和指令,以及本教程中描述的表單專(zhuān)用指令和技巧編寫(xiě)模板來(lái)構(gòu)建表單,或者你可以使用響應(yīng)式方式(或叫模型驅(qū)動(dòng)方式)來(lái)構(gòu)建表單。
模板驅(qū)動(dòng)表單適用于小型或簡(jiǎn)單的表單,而響應(yīng)式表單則更具伸縮性,適用于復(fù)雜表單。

你可以用 Angular 模板來(lái)構(gòu)建各種表單,比如登錄表單、聯(lián)系人表單和幾乎所有的業(yè)務(wù)表單。你可以創(chuàng)造性地對(duì)控件進(jìn)行布局并把它們綁定到對(duì)象模型的數(shù)據(jù)上。你可以指定驗(yàn)證規(guī)則并顯示驗(yàn)證錯(cuò)誤,有條不紊地啟用或禁用特定控件,觸發(fā)內(nèi)置的視覺(jué)反饋等等。

本教程將向你展示如何通過(guò)一個(gè)簡(jiǎn)化的范例表單來(lái)從頭構(gòu)建一個(gè)表單,就像“英雄之旅”教程的中用一個(gè)表單來(lái)講解這些技巧一樣。

運(yùn)行或下載范例應(yīng)用:現(xiàn)場(chǎng)演練 / 下載范例。

目標(biāo)

本教程將教你如何執(zhí)行以下操作:

  • 使用組件和模板構(gòu)建一個(gè) Angular 表單
  • 使用 ?ngModel ?創(chuàng)建雙向數(shù)據(jù)綁定,以便讀寫(xiě)輸入控件的值
  • 使用跟蹤控件狀態(tài)的特殊 CSS 類(lèi)來(lái)提供視覺(jué)反饋
  • 向用戶顯示驗(yàn)證錯(cuò)誤,并根據(jù)表單狀態(tài)啟用或禁用表單控件
  • 使用模板引用變量在 HTML 元素之間共享信息

構(gòu)建一個(gè)模板驅(qū)動(dòng)表單

模板驅(qū)動(dòng)表單依賴(lài)于 ?FormsModule ?定義的指令。

指令

詳細(xì)信息

NgModel

會(huì)協(xié)調(diào)其附著在的表單元素中的值變更與數(shù)據(jù)模型中的變更,以便你通過(guò)輸入驗(yàn)證和錯(cuò)誤處理來(lái)響應(yīng)用戶輸入。

NgForm

會(huì)創(chuàng)建一個(gè)頂級(jí)的 FormGroup 實(shí)例,并把它綁定到 <form> 元素上,以跟蹤它所聚合的那些表單值并驗(yàn)證狀態(tài)。只要你導(dǎo)入了 FormsModule,默認(rèn)情況下這個(gè)指令就會(huì)在所有 <form> 標(biāo)簽上激活。你不需要添加特殊的選擇器。

NgModelGroup

會(huì)創(chuàng)建 FormGroup 的實(shí)例并把它綁定到 DOM 元素中。

范例應(yīng)用

英雄雇傭管理局使用本指南中的范例表單來(lái)維護(hù)英雄的個(gè)人信息。畢竟英雄也要工作啊。這個(gè)表單有助于該機(jī)構(gòu)將正確的英雄與正確的危機(jī)匹配起來(lái)。


該表單突出了一些易于使用的設(shè)計(jì)特性。比如,這兩個(gè)必填字段的左邊是綠色條,以便讓它們醒目。這些字段都有初始值,所以表單是有效的,并且 Submit 按鈕也是啟用的。

當(dāng)你使用這個(gè)表單時(shí),你將學(xué)習(xí)如何包含驗(yàn)證邏輯,如何使用標(biāo)準(zhǔn) CSS 自定義表達(dá)式,以及如何處理錯(cuò)誤條件以確保輸入的有效性。比如,如果用戶刪除了英雄的名字,那么表單就會(huì)失效。該應(yīng)用會(huì)檢測(cè)已更改的狀態(tài),并以醒目的樣式顯示驗(yàn)證錯(cuò)誤。此外,Submit 按鈕會(huì)被禁用,輸入控件左側(cè)的“必填”欄也會(huì)從綠色變?yōu)榧t色。


步驟概述

在本教程中,你將使用以下步驟將一個(gè)范例表單綁定到數(shù)據(jù)并處理用戶輸入。

  1. 建立基本表單。
    • 定義一個(gè)范例數(shù)據(jù)模型
    • 包括必需的基礎(chǔ)設(shè)施,比如 ?FormsModule?
  2. 使用 ?ngModel ?指令和雙向數(shù)據(jù)綁定語(yǔ)法把表單控件綁定到數(shù)據(jù)屬性。
    • 檢查 ?ngModel ?如何使用 CSS 類(lèi)報(bào)告控件狀態(tài)
    • 為控件命名,以便讓 ?ngModel ?可以訪問(wèn)它們
  3. 用 ?ngModel ?跟蹤輸入的有效性和控件的狀態(tài)。
    • 添加自定義 CSS 來(lái)根據(jù)狀態(tài)提供可視化反饋
    • 顯示和隱藏驗(yàn)證錯(cuò)誤信息
  4. 通過(guò)添加到模型數(shù)據(jù)來(lái)響應(yīng)原生 HTML 按鈕的單擊事件
  5. 使用表單的 ?ngSubmit ?輸出屬性來(lái)處理表單提交。
    • 在表單生效之前,先禁用 Submit 按鈕
    • 在提交完成后,把已完成的表單替換成頁(yè)面上不同的內(nèi)容

建立表單

你可以根據(jù)這里提供的代碼從頭創(chuàng)建范例應(yīng)用,也可以查看 現(xiàn)場(chǎng)演練 / 下載范例

  1. 這里提供的范例應(yīng)用會(huì)創(chuàng)建一個(gè) ?Hero ?類(lèi),用于定義表單中所反映的數(shù)據(jù)模型。
  2. export class Hero {
    
      constructor(
        public id: number,
        public name: string,
        public power: string,
        public alterEgo?: string
      ) {  }
    
    }
  3. 該表單的布局和細(xì)節(jié)是在 ?HeroFormComponent? 類(lèi)中定義的。
  4. 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; }
    
    }

    該組件的 ?selector ?值為 “app-hero-form”,意味著你可以用 ?<app-hero-form>? 標(biāo)簽把這個(gè)表單放到父模板中。

  5. 下面的代碼會(huì)創(chuàng)建一個(gè)新的 hero 實(shí)例,以便讓初始的表單顯示一個(gè)范例英雄。
  6. 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"

    這個(gè)演示使用虛擬數(shù)據(jù)來(lái)表達(dá) ?model ?和 ?powers?。在真正的應(yīng)用中,你會(huì)注入一個(gè)數(shù)據(jù)服務(wù)來(lái)獲取和保存實(shí)際數(shù)據(jù),或者把它們作為輸入屬性和輸出屬性進(jìn)行公開(kāi)。

  7. 該應(yīng)用啟用了表單功能,并注冊(cè)了已創(chuàng)建的表單組件。
  8. import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { CommonModule } from '@angular/common';
    import { FormsModule } from '@angular/forms';
    
    import { AppComponent } from './app.component';
    import { HeroFormComponent } from './hero-form/hero-form.component';
    
    @NgModule({
      imports: [
        BrowserModule,
        CommonModule,
        FormsModule
      ],
      declarations: [
        AppComponent,
        HeroFormComponent
      ],
      providers: [],
      bootstrap: [ AppComponent ]
    })
    export class AppModule { }
  9. 該表單顯示在根組件模板定義的應(yīng)用布局中。
  10. <app-hero-form></app-hero-form>

    初始模板定義了一個(gè)帶有兩個(gè)表單組和一個(gè)提交按鈕的表單布局。表單組對(duì)應(yīng)于 Hero 數(shù)據(jù)模型的兩個(gè)屬性:name 和 alterEgo。每個(gè)組都有一個(gè)標(biāo)簽和一個(gè)用戶輸入框。

    • Name ?<input>? 控件元素中包含了 HTML5 的 ?required ?屬性
    • Alter Ego ?<input>? 沒(méi)有控件元素,因?yàn)?nbsp;?alterEgo ?是可選的

    Submit 按鈕里面有一些用于樣式化的類(lèi)。此時(shí),表單布局全都是純 HTML5,沒(méi)有綁定或指令。

  11. 范例表單使用的是 Twitter Bootstrap 中的一些樣式類(lèi):?container?,?form-group?,?form-control? 和 ?btn?。要使用這些樣式,就要在該應(yīng)用的樣式表中導(dǎo)入該庫(kù)。
  12. @import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
  13. 這份表單讓英雄申請(qǐng)人從管理局批準(zhǔn)過(guò)的固定清單中選出一項(xiàng)超能力。預(yù)定義 ?powers ?列表是數(shù)據(jù)模型的一部分,在 ?HeroFormComponent ?內(nèi)部維護(hù)。Angular 的?NgForOf ?指令會(huì)遍歷這些數(shù)據(jù)值,以填充這個(gè) ?<select>? 元素。
  14. <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>

如果你現(xiàn)在正在運(yùn)行該應(yīng)用,你會(huì)看到選擇控件中的超能力列表。由于尚未將這些 input 元素綁定到數(shù)據(jù)值或事件,因此它們?nèi)匀皇强瞻椎模瑳](méi)有任何行為。


把輸入控件綁定到數(shù)據(jù)屬性

下一步是使用雙向數(shù)據(jù)綁定把輸入控件綁定到相應(yīng)的 ?Hero ?屬性,這樣它們就可以通過(guò)更新數(shù)據(jù)模型來(lái)響應(yīng)用戶的輸入,并通過(guò)更新顯示來(lái)響應(yīng)數(shù)據(jù)中的程序化變更。

該 ?ngModel ?指令是由 ?FormsModule ?聲明的,它能讓你把模板驅(qū)動(dòng)表單中的控件綁定到數(shù)據(jù)模型中的屬性。當(dāng)你使用雙向數(shù)據(jù)綁定的語(yǔ)法 ?[(ngModel)]? 引入該指令時(shí),Angular 就可以跟蹤控件的值和用戶交互,并保持視圖與模型的同步。

  1. 編輯模板 ?hero-form.component.html?。
  2. 找到 Name 標(biāo)簽旁邊的 ?<input>? 標(biāo)記。
  3. 使用雙向數(shù)據(jù)綁定語(yǔ)法 ?[(ngModel)]="..."? 添加 ?ngModel ?指令。
<input type="text" class="form-control" id="name"
       required
       [(ngModel)]="model.name" name="name">
TODO: remove this: {{model.name}}

這個(gè)例子中在每個(gè) input 標(biāo)記后面都有一個(gè)臨時(shí)的診斷插值 ?{{model.name}}?,以顯示相應(yīng)屬性的當(dāng)前數(shù)據(jù)值。本提醒是為了讓你在觀察完這個(gè)雙向數(shù)據(jù)綁定后刪除這些診斷行。

訪問(wèn)表單的整體狀態(tài)

當(dāng)你導(dǎo)入了 ?FormsModule ?時(shí),Angular 會(huì)自動(dòng)為模板中的 ?<form>? 標(biāo)簽創(chuàng)建并附加一個(gè) ?NgForm ?指令。(因?yàn)?nbsp;?NgForm ?定義了一個(gè)能匹配 ?<form>? 元素的選擇器 ?form?)。

要訪問(wèn) ?NgForm ?和表單的整體狀態(tài),就要聲明一個(gè)模板引用變量

  1. 編輯模板 ?hero-form.component.html?。
  2. 為 ?<form>? 標(biāo)簽添加模板引用變量 ?#heroForm?,并把它的值設(shè)置如下。
  3. <form #heroForm="ngForm">

    模板變量 ?heroForm ?現(xiàn)在是對(duì) ?NgForm ?指令實(shí)例的引用,該指令實(shí)例管理整個(gè)表單。

  4. 運(yùn)行該應(yīng)用。
  5. 開(kāi)始在 Name 輸入框中輸入。
  6. 在添加和刪除字符時(shí),你可以看到它們從數(shù)據(jù)模型中出現(xiàn)和消失。比如:


    用來(lái)顯示插值的診斷行證明了這些值確實(shí)從輸入框流向了模型,然后再返回。

為控件元素命名

在元素上使用 ?[(ngModel)]? 時(shí),必須為該元素定義一個(gè) ?name ?屬性。Angular 會(huì)用這個(gè)指定的名字來(lái)把這個(gè)元素注冊(cè)到父 ?<form>? 元素上的 ?NgForm ?指令中。

這個(gè)例子中為 ?<input>? 元素添加了一個(gè) ?name ?屬性,并把它的值設(shè)置為 “name”,用來(lái)表示英雄的名字。任何唯一的值都可以用,但最好用描述性的名稱(chēng)。

  1. Alter EgoHero Power添加類(lèi)似的 ?[(ngModel)]? 綁定和 ?name ?屬性。
  2. 你現(xiàn)在可以移除顯示插值的診斷消息了。
  3. 要想確認(rèn)雙向數(shù)據(jù)綁定是否在整個(gè)英雄模型上都有效,可以在該組件的頂部添加一個(gè)帶有 ?json ?管道的新文本綁定。?json ?管道會(huì)把數(shù)據(jù)序列化為字符串。
  4. 表單模板修改完畢后,應(yīng)如下所示:

    {{ 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="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>
    • 注意,每個(gè) ?<input>? 元素都有一個(gè) ?id ?屬性。?<label>? 元素的 ?for ?屬性用它來(lái)把標(biāo)簽匹配到輸入控件。這是一個(gè)標(biāo)準(zhǔn)的 HTML 特性。
    • 每個(gè) ?<input>? 元素都有一個(gè)必需的 ?name ?屬性,Angular 用它來(lái)注冊(cè)表單中的控件。

    如果你現(xiàn)在運(yùn)行該應(yīng)用并更改英雄模型的每個(gè)屬性,該表單可能會(huì)顯示如下:


    通過(guò)表單頂部的診斷行可以確認(rèn)所有的更改都已反映在模型中。

  5. 你已經(jīng)觀察到了這種效果,可以刪除 ?{{ model | json }}? 的文本綁定了。

跟蹤控件狀態(tài)

控件上的 ?NgModel ?指令會(huì)跟蹤該控件的狀態(tài)。它會(huì)告訴你用戶是否接觸過(guò)該控件、該值是否發(fā)生了變化,或者該值是否無(wú)效。Angular 在控件元素上設(shè)置了特殊的 CSS 類(lèi)來(lái)反映其狀態(tài),如下表所示。

狀態(tài)

為 TRUE 時(shí)的類(lèi)名

為 FALSE 時(shí)的類(lèi)名

該控件已被訪問(wèn)過(guò)。

ng-touched ng-untouched

控件的值已被更改。

ng-dirty ng-pristine

控件的值是有效的。

ng-valid ng-invalid

此外,Angular 還會(huì)在提交時(shí)把 ?ng-submitted? 類(lèi)應(yīng)用到 ?<form>? 元素上。這個(gè)類(lèi)不會(huì)應(yīng)用到內(nèi)部控件上。

你可以用這些 CSS 類(lèi)來(lái)根據(jù)控件的狀態(tài)定義其樣式。

觀察控件狀態(tài)

要想知道框架是如何添加和移除這些類(lèi)的,請(qǐng)打開(kāi)瀏覽器的開(kāi)發(fā)者工具,檢查代表英雄名字的 ?<input>?

  1. 使用瀏覽器的開(kāi)發(fā)者工具,找到與 “Name” 輸入框?qū)?yīng)的 ?<input>? 元素。除了 “form-control” 類(lèi)之外,你還可以看到該元素有多個(gè) CSS 類(lèi)。
  2. 當(dāng)你第一次啟動(dòng)它的時(shí)候,這些類(lèi)表明它是一個(gè)有效的值,該值在初始化或重置之后還沒(méi)有改變過(guò),并且在該控件自初始化或重置后也沒(méi)有被訪問(wèn)過(guò)。
  3. <input … class="form-control ng-untouched ng-pristine ng-valid" …>
  4. 在 Name ?<input>? 框中執(zhí)行以下操作,看看會(huì)出現(xiàn)哪些類(lèi)。
    • 查看,但不要碰它。這些類(lèi)表明它沒(méi)有被碰過(guò)、還是最初的值,并且有效。
    • 在 Name 框內(nèi)單擊,然后單擊它外部。該控件現(xiàn)在已被訪問(wèn)過(guò),該元素具有 ?ng-touched? 類(lèi),取代了 ?ng-untouched? 類(lèi)。
    • 在名字的末尾添加斜杠?,F(xiàn)在它被碰過(guò),而且是臟的(變化過(guò))。
    • 刪掉這個(gè)名字。這會(huì)使該值無(wú)效,所以 ?ng-invalid? 類(lèi)會(huì)取代 ?ng-valid? 類(lèi)。

為狀態(tài)創(chuàng)建視覺(jué)反饋

注意 ?ng-valid? / ?ng-invalid? 這兩個(gè)類(lèi),因?yàn)槟阆朐谥禑o(wú)效時(shí)發(fā)出強(qiáng)烈的視覺(jué)信號(hào)。你還要標(biāo)記必填字段。

你可以在輸入框的左側(cè)用彩條標(biāo)記必填字段和無(wú)效數(shù)據(jù):


要想用這種方式修改外觀,請(qǐng)執(zhí)行以下步驟。

  1. 為 ?ng-*? CSS 類(lèi)添加一些定義。
  2. 把這些類(lèi)定義添加到一個(gè)新的 ?forms.css? 文件中。
  3. 把這個(gè)新文件添加到項(xiàng)目中,作為 ?index.html? 的兄弟:
  4. .ng-valid[required], .ng-valid.required  {
      border-left: 5px solid #42A948; /* green */
    }
    
    .ng-invalid:not(form)  {
      border-left: 5px solid #a94442; /* red */
    }
  5. 在 ?index.html? 文件中,更新 ?<head>? 標(biāo)簽以包含新的樣式表。
  6. <link rel="stylesheet" href="assets/forms.css">

顯示和隱藏驗(yàn)證錯(cuò)誤信息

Name 輸入框是必填的,清除它就會(huì)把彩條變成紅色。這表明有些東西是錯(cuò)的,但是用戶并不知道要怎么做或該做什么。你可以通過(guò)查看和響應(yīng)控件的狀態(tài)來(lái)提供有用的信息。

當(dāng)用戶刪除該名字時(shí),該表單應(yīng)如下所示:


Hero Power 選擇框也是必填的,但它不需要這樣的錯(cuò)誤處理,因?yàn)檫x擇框已經(jīng)把選擇限制在有效值范圍內(nèi)。

要在適當(dāng)?shù)臅r(shí)候定義和顯示錯(cuò)誤信息,請(qǐng)執(zhí)行以下步驟。

  1. 使用模板引用變量擴(kuò)展 ?<input>? 標(biāo)簽,你可以用來(lái)從模板中訪問(wèn)輸入框的 Angular 控件。在這個(gè)例子中,該變量是 ?#name="ngModel"?。
  2. 模板引用變量(?#name?)設(shè)置為 ?"ngModel"?,因?yàn)?nbsp;"ngModel" 是 ?NgModel.exportAs? 屬性的值。這個(gè)屬性告訴 Angular 如何把引用變量和指令鏈接起來(lái)。

  3. 添加一個(gè)包含合適錯(cuò)誤信息 ?<div> ?
  4. 通過(guò)把 ?name ?控件的屬性綁定到 ?<div>? 元素的 ?hidden ?屬性來(lái)顯示或隱藏錯(cuò)誤信息。
  5. <div [hidden]="name.valid || name.pristine"
         class="alert alert-danger">
  6. 為 ?name ?輸入框添加一個(gè)有條件的錯(cuò)誤信息,如下例所示。
  7. <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>
關(guān)于 "PRISTINE"(原始)狀態(tài)的說(shuō)明
在這個(gè)例子中,當(dāng)控件是有效的(valid)或者是原始的(pristine)時(shí),你會(huì)隱藏這些消息。原始表示該用戶在此表單中顯示的值尚未更改過(guò)。如果你忽略了 ?pristine ?狀態(tài),那么只有當(dāng)值有效時(shí)才會(huì)隱藏這些消息。如果你把一個(gè)新的(空白)英雄或一個(gè)無(wú)效的英雄傳給這個(gè)組件,你會(huì)立刻看到錯(cuò)誤信息,而這時(shí)候你還沒(méi)有做過(guò)任何事情。
你可能希望只有在用戶做出無(wú)效更改時(shí),才顯示該消息。因此當(dāng) ?pristine ?狀態(tài)時(shí),隱藏這條消息就可以滿足這個(gè)目標(biāo)。當(dāng)你在下一步中為表單添加一個(gè)新的英雄時(shí),就會(huì)看到這個(gè)選擇有多重要。

添加一個(gè)新英雄

本練習(xí)通過(guò)添加模型數(shù)據(jù),展示了如何響應(yīng)原生 HTML 按鈕單擊事件。要讓表單用戶添加一個(gè)新的英雄,就要添加一個(gè)能響應(yīng) click 事件的 New Hero 按鈕。

  1. 在模板中,把 “New Hero” 這個(gè) ?<button>? 元素放在表單底部。
  2. 在組件文件中,把創(chuàng)建英雄的方法添加到英雄數(shù)據(jù)模型中。
  3. newHero() {
      this.model = new Hero(42, '', '');
    }
  4. 把按鈕的 click 事件綁定到一個(gè)創(chuàng)建英雄的方法 ?newHero()? 上。
  5. <button type="button" class="btn btn-default" (click)="newHero()">New Hero</button>
  6. 再次運(yùn)行該應(yīng)用,單擊 New Hero 按鈕。
  7. 表單會(huì)清空,輸入框左側(cè)的必填欄會(huì)顯示紅色,說(shuō)明 ?name ?和 ?power ?屬性無(wú)效。請(qǐng)注意,錯(cuò)誤消息是隱藏的。這是因?yàn)楸韱翁幱谠紶顟B(tài)。你還沒(méi)有改過(guò)任何東西。

  8. 輸入一個(gè)名字,然后再次點(diǎn)擊 New Hero。
  9. 現(xiàn)在,該應(yīng)用會(huì)顯示一條錯(cuò)誤信息 ?Name is required?,因?yàn)樵撦斎肟虿辉偈窃紶顟B(tài)。表單會(huì)記住你在單擊 New Hero 之前輸入過(guò)一個(gè)名字。

  10. 要恢復(fù)表單控件的原始狀態(tài),可以在調(diào)用 ?newHero()? 方法之后強(qiáng)制調(diào)用表單的 ?reset()? 方法以清除所有標(biāo)志。
  11. <button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button>

    現(xiàn)在單擊 New Hero 會(huì)重置表單及其控件標(biāo)志。

使用 ngSubmit 提交表單

用戶應(yīng)該可以在填寫(xiě)之后提交這個(gè)表單。表單底部的 Submit 按鈕本身沒(méi)有任何作用,但由于它的類(lèi)型(?type="submit"?),它會(huì)觸發(fā)一個(gè)表單提交事件。要響應(yīng)此事件,請(qǐng)執(zhí)行以下步驟。

  1. 把表單的 ?ngSubmit ?事件屬性綁定到一個(gè) hero-form 組件的 ?onSubmit()? 方法中。
  2. <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
  3. 使用模板引用變量 ?#heroForm? 訪問(wèn)包含 Submit 按鈕的表單,并創(chuàng)建一個(gè)事件綁定。你可以把表示它整體有效性的 form 屬性綁定到 Submit 按鈕的 ?disabled ?屬性上。
  4. <button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>
  5. 運(yùn)行該應(yīng)用。注意,該按鈕已啟用 - 雖然它還沒(méi)有做任何有用的事情。
  6. 刪除名稱(chēng)值。這違反了“必需”規(guī)則,因此會(huì)顯示錯(cuò)誤消息,并注意它還會(huì)禁用“提交”按鈕。
  7. 你不必把按鈕的啟用狀態(tài)明確地關(guān)聯(lián)表單的有效性上。當(dāng) ?FormsModule ?在增強(qiáng)的表單元素上定義模板引用變量時(shí),會(huì)自動(dòng)執(zhí)行此操作,然后在按鈕控件中引用該變量。

響應(yīng)表單提交

要展示對(duì)表單提交的響應(yīng),你可以隱藏?cái)?shù)據(jù)輸入?yún)^(qū)域并就地顯示其它內(nèi)容。

  1. 把整個(gè)表單包裹進(jìn)一個(gè) ?<div>? 中并把它的 ?hidden ?屬性綁定到 ?HeroFormComponent.submitted? 屬性上。
  2. <div [hidden]="submitted">
      <h1>Hero Form</h1>
      <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
    
         <!-- ... all of the form ... -->
    
      </form>
    </div>
    • 主表單從一開(kāi)始就是可見(jiàn)的,因?yàn)樵谔峤恢?,它?nbsp;?submitted ?屬性都是 false,正如 ?HeroFormComponent ?中的這個(gè)片段所顯示的:
    • submitted = false;
      
      onSubmit() { this.submitted = true; }
    • 點(diǎn)擊 Submit 按鈕后,?submitted ?標(biāo)志就變?yōu)?nbsp;?true?,表單就會(huì)消失。
  3. 要在表單處于已提交狀態(tài)時(shí)顯示其它內(nèi)容,請(qǐng)?jiān)谛碌?nbsp;?<div>? 包裝器下添加以下 HTML。
  4. <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 type="button" class="btn btn-primary" (click)="submitted=false">Edit</button>
    </div>

    這個(gè) ?<div>?(用于顯示帶插值綁定的只讀英雄)只在組件處于已提交狀態(tài)時(shí)才會(huì)出現(xiàn)。

    另外還顯示了一個(gè) Edit 按鈕,它的 click 事件綁定到了一個(gè)清除 ?submitted ?標(biāo)志的表達(dá)式。

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

總結(jié)

本頁(yè)討論的 Angular 表單利用了下列框架特性來(lái)支持?jǐn)?shù)據(jù)修改,驗(yàn)證等工作。

  • 一個(gè) Angular HTML 表單模板
  • 帶 ?@Component? 裝飾器的表單組件類(lèi)
  • 綁定到 ?NgForm.ngSubmit? 事件屬性來(lái)處理表單提交
  • 模板引用變量,比如 ?#heroForm? 和 ?#name ?
  • 雙向數(shù)據(jù)綁定的 ?[(ngModel)]? 語(yǔ)法
  • ?name ?屬性的用途是驗(yàn)證和表單元素的變更跟蹤
  • 用輸入控件上的引用變量的 ?valid? 屬性來(lái)檢查控件是否有效,并據(jù)此顯示或隱藏錯(cuò)誤信息
  • 用 ?NgForm ?的有效性來(lái)控制 Submit 按鈕的啟用狀態(tài)
  • 自定義 CSS 類(lèi),為用戶提供關(guān)于無(wú)效控件的視覺(jué)反饋

這里是該應(yīng)用最終版本的代碼:

  • hero-form/hero-form.component.ts
  • 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, '', '');
      }
    }
  •  hero-form/hero-form.component.html
  • <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 type="button" class="btn btn-primary" (click)="submitted=false">Edit</button>
      </div>
    </div>
  • hero.ts
  • export class Hero {
    
      constructor(
        public id: number,
        public name: string,
        public power: string,
        public alterEgo?: string
      ) {  }
    
    }
  • app.module.ts
  • import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { CommonModule } from '@angular/common';
    import { FormsModule } from '@angular/forms';
    
    import { AppComponent } from './app.component';
    import { HeroFormComponent } from './hero-form/hero-form.component';
    
    @NgModule({
      imports: [
        BrowserModule,
        CommonModule,
        FormsModule
      ],
      declarations: [
        AppComponent,
        HeroFormComponent
      ],
      providers: [],
      bootstrap: [ AppComponent ]
    })
    export class AppModule { }
  • app.component.html
  • <app-hero-form></app-hero-form>
  • app.component.ts
  • import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent { }
  • main.ts
  • 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);
  • forms.css
  • .ng-valid[required], .ng-valid.required  {
      border-left: 5px solid #42A948; /* green */
    }
    
    .ng-invalid:not(form)  {
      border-left: 5px solid #a94442; /* red */
    }


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)