表單

版本:Angular 5.0.0-alpha

表單是商業應用的支柱,我們用它來執行登錄、求助、下單、預訂機票、安排會議,以及不計其數的其它數據錄入任務。

在開發表單時,創建數據方面的體驗是非常重要的,它能指引用戶明細、高效的完成工作流程。

開發表單需要設計能力(那超出了本章的范圍),而框架支持雙向數據綁定、變更檢測、驗證和錯誤處理,在本章你將會學習它們。

本章展示了如何從草稿構建一個簡單的表單。在這個過程中你將學會如何:

  • 用組件和模板構建 Angular 表單。
  • ngModel創建雙向數據綁定,以讀取和寫入輸入控件的值。
  • 跟蹤狀態的變化,并驗證表單控件。
  • 使用特殊的 CSS 類來跟蹤控件的狀態并給出視覺反饋。
  • 向用戶顯示驗證錯誤提示,以及啟用/禁用表單控件。
  • 使用模板引用變量在 HTML 元素之間共享信息。

你可以運行在線示例(查看源代碼)。

模板驅動表單

你可以使用 Angular 模板語法編寫模板,結合本章所描述的表單專用指令和技術來構建表單。

你還可以使用響應式(也叫模型驅動)的方式來構建表單。不過本章中只介紹模板驅動表單。

利用 Angular 模板,可以構建幾乎所有表單——登錄表單、聯系人表單, 以及任何非常漂亮的商務表單。可以創造性的擺放各種控件、把它們綁定到數據、指定校驗規則、顯示校驗錯誤、有條件的禁用或啟用特定的控件、觸發內置的視覺反饋等等,不勝枚舉。

Angular 通過處理大量重復的、模板化的任務,簡化了過程,從而使你不必陷入與自己的斗爭中。

你將學習構建如下的“模板驅動”表單:

英雄職業介紹所,使用這個表單來維護英雄們的個人信息。每個英雄都需要一份工作。公司的使命就是讓合適的英雄去應對合適的危機。

表單中的三個字段,其中兩個是必填的。根據 material design 指南,必填的字段用星號(*)標出。

如果刪除了英雄的名字,表單就會用醒目的樣式把驗證錯誤顯示出來。

注意,提交按鈕被禁用了,而且輸入控件從綠色變為了紅色。

你將一小步一小步地構建此表單:

  1. 創建Hero模型類。
  2. 創建控制此表單的組件。
  3. 創建具有初始表單布局的模板。
  4. 使用 ngModel 雙向數據綁定語法把數據屬性綁定到每個表單輸入控件。
  5. 為每個表單輸入控件添加 ngControl 指令。
  6. 添加自定義 CSS 來提供視覺反饋。
  7. 顯示和隱藏有效性驗證的錯誤信息。
  8. 使用 ngSubmit 處理表單提交。
  9. 禁用此表單的提交按鈕,直到表單變為有效。

配置

根據配置的說明創建一個名為forms的新項目。

添加 angular_forms

Angular 表單的功能在 angular_forms 庫中,它有自己的包,添加包到 pub 依賴中:

// {quickstart → forms}/pubspec.yaml

dependencies:
    angular: ^5.0.0-alpha       
+  angular_forms: ^2.0.0-alpha

創建模型

當用戶輸入表單數據時,需要捕獲它們的變化,并更新到模型的實例中。除非知道模型的樣子,否則無法設計表單的布局。

最簡單的模型是個“屬性包”,用來存放關于應用重點的資料。這里使用了描述Hero類的三個必備字段 (idnamepower),和一個可選字段 (alterEgo)。

lib目錄,按照已給出的內容創建下面的文件:

// lib/src/hero.dart

class Hero {
  int id;
  String name, power, alterEgo;
  Hero(this.id, this.name, this.power, [this.alterEgo]);
  String toString() => '$id: $name ($alterEgo). Super power: $power';
}  

這是一個少量需求和零行為的貧血模型。對演示來說足夠了。

alterEgo是可選的,所以構造函數允許你省略它;注意在[this.alterEgo]中的方括號。

可以像這樣創建新英雄:

var myHero = new Hero(
    42, 'SkyDog', 'Fetch any object at any distance', 'Leslie Rollover');
print('My hero is ${myHero.name}.'); // "My hero is SkyDog."

創建基本的表單

Angular 表單分為兩部分:基于 HTML 的模板 ,以及用來處理數據和用戶動態交互的組件類。先從這個類開始,是因為它可以簡要說明英雄編輯器能做什么。

創建表單組件

根據已給出的內容創建下面的文件:

// lib/src/hero_form_component.dart (v1)

import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';

import 'hero.dart';

const List<String> _powers = [
  'Really Smart',
  'Super Flexible',
  'Super Hot',
  'Weather Changer'
];

@Component(
  selector: 'hero-form',
  templateUrl: 'hero_form_component.html',
  directives: [coreDirectives, formDirectives],
)
class HeroFormComponent {
  Hero model = new Hero(18, 'Dr IQ', _powers[0], 'Chuck Overstreet');
  bool submitted = false;

  List<String> get powers => _powers;

  void onSubmit() => submitted = true;
}

這個組件沒有什么特別的地方,沒有表單相關的東西,與之前寫過的組件沒什么不同。

只需要前面章節中學過的 Angular 概念,就可以完全理解這個組件:

  • 這段代碼導入了 Angular 核心庫以及你剛剛創建的Hero模型。
  • @Component選擇器hero-form表示可以用<hero-form>元素把這個表單放進父模板。
  • templateUrl屬性指向一個獨立的 HTML 模板文件(稍后創建)。
  • modelpowers定義模擬數據。

接下來,你可以注入一個數據服務,以獲取或保存真實的數據,或者把這些屬性暴露為輸入屬性和輸出屬性(參見模板語法中的輸入和輸出屬性)來綁定到一個父組件。這不是現在需要關心的問題,未來的更改不會影響到這個表單。

修改 app component

AppComponent 是應用的根組件,HeroFormComponent 將被放在其中。

使用下面的內容替換初始版本:

// lib/app_component.dart

import 'package:angular/angular.dart';

import 'src/hero_form_component.dart';

@Component(
  selector: 'my-app',
  template: '<hero-form></hero-form>',
  directives: [HeroFormComponent],
)
class AppComponent {}

創建初始 HTML 表單模板

使用下面的內容創建模板文件:

// lib/src/hero_form_component.html (start)

<div class="container">
  <h1>Hero Form</h1>
  <form>
    <div class="form-group">
      <label for="name">Name&nbsp;*</label>
      <input type="text" class="form-control" id="name" required>
    </div>
    <div class="form-group">
      <label for="alterEgo">Alter Ego</label>
      <input type="text" class="form-control" id="alterEgo">
    </div>
    <div class="row">
      <div class="col-auto">
        <button type="submit" class="btn btn-primary">Submit</button>
      </div>
      <small class="col text-right">*&nbsp;Required</small>
    </div>
  </form>
</div>

這是一段簡單的 HTML5 代碼。我們展現了Hero的兩個字段,namealterEgo,提供給用戶在輸入框中輸入。

Name<input>控件具有 HTML5 的required屬性;Alter Ego<input>控件沒有,因為alterEgo是可選的。

在底部添加了一個具有一些 CSS 類的提交按鈕。

你還沒有用到 Angular。沒有綁定,沒有額外的指令,只有布局。

在模板驅動表單中,你只要導入了angular_forms庫,就不用對<form>做任何其它的事情來使用庫的功能。接下來你會看到它的原理。

刷新瀏覽器。你會看到一個簡單的,沒有樣式的表單。

給表單添加樣式

containerbtn類都來自 Bootstrap。Bootstrap 也有特定的表單類,包括form-controlform-group。它們給表單添加了一點樣式。

Angular 不需要使用 Bootstrap 類或任意外部庫的樣式。Angular 應用可以使用任意 CSS 庫或一點也不用。

index.html<head>插入下面的鏈接來添加 Bootstrap 樣式。

// web/index.html (bootstrap)

<link rel="stylesheet"  integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">

刷新瀏覽器。你會看到一個帶有樣式的表單。

使用 *ngFor 添加 powers

英雄必須從認證過的固定列表中選擇一項超能力。你在內部維護這個列表(在HeroFormComponent)。

在表單中添加select,用ngForpowers列表綁定到列表選項,在之前的顯示數據一章中使用過的技術。

在緊跟著 Alter Ego 組的下方添加如下 HTML:

// lib/src/hero_form_component.html (powers)

<div class="form-group">
  <label for="power">Hero Power&nbsp;*</label>
  <select class="form-control" id="power" required>
    <option *ngFor="let p of powers" [value]="p">{{p}}</option>
  </select>
</div>

powers列表中的每一項超能力都會渲染成<option>標簽。 模板輸入變量p在每個迭代指向不同的超能力,使用雙花括號插值表達式語法來顯示它的名稱。

使用 ngModel 雙向數據綁定

現在運行此應用,有點令人失望。

你看不到英雄數據因為還沒有綁定到Hero。在前面的章節我們知道怎么去做。顯示數據介紹了屬性綁定。用戶輸入展示了如何通過事件綁定來監聽 DOM 事件,以及如何用顯示的值更新組件的屬性。

現在,需要同時進行顯示、監聽和提取。

雖然可以在表單中再次使用這些已知的技術。但是,你將使用新的[(ngModel)]語法,使表單綁定到模型的工作變得更容易。

找到Name對應的<input>標簽,并且像這樣更新它:

// lib/src/hero_form_component.html (name)

<!-- TODO: remove the next diagnostic line -->
<mark>{{model.name}}</mark><hr>
<div class="form-group">
  <label for="name">Name&nbsp;*</label>
  <input type="text" class="form-control" id="name" required
         [(ngModel)]="model.name"
         ngControl="name">
</div>

在 form-group 標簽前添加用于診斷的插值表達式,以看清正在發生什么事。給自己留個注釋,提醒你完成后移除它。

聚焦到綁定語法:[(ngModel)]="..."上。

現在運行應用,開始在Name 輸入框中鍵入,添加和刪除字符,我們將看到它們從診斷文本中顯示和消失。某一瞬間,它看起來可能是這樣:

診斷信息可以證明,數據確實從輸入框流動到模型,再反向流動回來。

這就是雙向數據綁定!。更多信息,參見模板語法章節的使用 NgModel 雙向綁定

注意,<input>標簽還添加了ngControl指令,并設置為 "name",表示英雄的名字。使用任何唯一的值都可以,但使用具有描述性的“name”會更有幫助。當在表單組合中使用[(ngModel)]時,必須要定義ngControl指令。

在內部,Angular 創建了一些NgFormControl,并把它們注冊到NgForm指令,再將該指令附加到<form>標簽。每個NgFormControl都都以你分配給NgFormControl指令的名稱注冊。稍后會看到更多 NgForm 的信息。

Alter EgoHero Power 添加類似的[(ngModel)]綁定和ngControl指令。

使用model替換診斷綁定表達式。這樣就能確認雙向數據綁定在整個 Hero 模型上都能正常工作了。

修改之后,這個表單的核心是這樣的:

// lib/src/hero_form_component.html (controls)

<!-- TODO: remove the next diagnostic line -->
<mark>{{model}}</mark><hr>
<div class="form-group">
  <label for="name">Name&nbsp;*</label>
  <input type="text" class="form-control" id="name" required
         [(ngModel)]="model.name"
         ngControl="name">
</div>
<div class="form-group">
  <label for="alterEgo">Alter Ego</label>
  <input type="text" class="form-control" id="alterEgo"
         [(ngModel)]="model.alterEgo"
         ngControl="alterEgo">
</div>
<div class="form-group">
  <label for="power">Hero Power&nbsp;*</label>
  <select class="form-control" id="power" required
          [(ngModel)]="model.power"
          ngControl="power">
    <option *ngFor="let p of powers" [value]="p">{{p}}</option>
  </select>
</div>
  • 每個 input 元素都有id屬性,label元素的for屬性用它來匹配到對應的輸入控件。
  • 每個 input 元素都有ngControl指令,這是 Angular 表單注冊表單控件所必須的。

如果現在運行本應用,修改每個 Hero 模型的屬性,表單可能顯示如下:

表單頂部的診斷信息證實了你所做的一切更改都反映在了 model 中。

從模板刪除診斷綁定因為它已經完成了它的使命。

基于控件狀態提供視覺反饋

使用 CSS 和類綁定,可以改變表單控件的外觀來反映它的狀態。

追蹤控件狀態

一個 Angular 表單控件可以告訴你,用戶是否碰過此控件,值是否發生改變,以及值是否無效。

Angular 表單的每個控件(NgControl)追蹤自身的狀態,并通過檢查下面的成員字段使狀態可用:

  • dirtypristine表明控件的值是否發生改變。
  • toucheduntouched表明控件是否被訪問。
  • valid反映了控件值的有效性。

控件樣式

valid控件屬性是最引人注意的,因為當控件的值無效時,你希望發出強烈的視覺信號。要創建這樣的視覺反饋,你需要使用 Bootstrap custom-forms 的類is-validis-invalid

Name<input>標簽添加一個名為name的模板引用變量。使用name和類綁定有條件的指定恰當的表單有效性的類。

Name<input>標簽臨時添加另一個名為spy的模板引用變量,用來顯示輸入框的 CSS 類。

// lib/src/hero_form_component.html (name)

<input type="text" class="form-control" id="name" required
       [(ngModel)]="model.name"
       #name="ngForm"
       #spy
       [class.is-valid]="name.valid"
       [class.is-invalid]="!name.valid"
       ngControl="name">
<!-- TODO: remove the next diagnostic line -->
{{spy.className}}
模板引用變量

spy模板引用變量綁定到了<input>DOM元素,然而name變量(#name="ngForm")綁定到了與 input 元素相關聯的 NgModel。

為什么是 “ngForm”?指令exportAs 屬性告訴 Angular 如何鏈接模板引用變量到指令。把name設置為 “ngForm” 是因為 ngModel 指令的exportAs屬性是 “ngForm”。

刷新瀏覽器,遵循下面的步驟:

  1. Name 輸入框。
    • 它有一個綠色的邊框。
    • 它有form-controlis-valid類。
  2. 添加一些字符來改變 name。類名依然不變。
  3. 刪除 name。
    • 輸入邊框變紅。
    • is-invalid類變成了is-valid

刪除#spy模板引用變量和使用到它的診斷信息。

另一種類綁定的方法,可以使用 NgClass 指令給控件添加樣式。首先添加下面的方法來設置控件的狀態依賴 CSS 類名:

// lib/src/hero_form_component.dart (setCssValidityClass)

Map<String, bool> setCssValidityClass(NgControl control) {
  final validityClass = control.valid == true ? 'is-valid' : 'is-invalid';
  return {validityClass: true};
}

使用上面方法返回的 map 值,綁定到 NgClass 指令——更多關于這個指令及其替代品的信息請看模板語法章節。

// lib/src/hero_form_component.html (power)

<select class="form-control" id="power" required
        [(ngModel)]="model.power"
        #power="ngForm"
        [ngClass]="setCssValidityClass(power)"
        ngControl="power">
  <option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>

顯示和隱藏驗證錯誤信息

你可以改進表單。Name 輸入框是必填的,清空它會使輸入框變紅。這表明有些東西錯了,但用戶不知道錯在哪里,或者如何糾正。利用控件狀態來顯示有用的信息。

使用 valid 和 pristine 狀態

當用戶刪除姓名時,表單看起來應該是這樣的:

要達到這個效果,在緊跟著 Name <input>標簽后面添加下面的<div>

// lib/src/hero_form_component.html (hidden error message)

<div [hidden]="name.valid || name.pristine" class="invalid-feedback">
  Name is required
</div>

刷新瀏覽器,刪除輸入框中的Name。錯誤信息就顯示出來了。

基于name控件的狀態,通過設置divhidden 屬性,顯式地控制錯誤信息。

在這個例子中,當控件是 valid 或 pristine 時,隱藏消息。 “pristine” 意味著從它被顯示在表單中開始,用戶還從未修改過它的值。

用戶體驗取決于開發人員的選擇

有些開發人員會希望任何時候都顯示這條消息。如果忽略了 pristine 狀態,就會只在值有效時隱藏此消息。如果往這個組件中傳入全新(空)的英雄,或者無效的英雄,將立刻看到錯誤信息 —— 雖然你還什么都沒做。

有些開發人員會希望只有在用戶做出無效的更改時才顯示這個消息。 如果當控件是 “pristine” 狀態時也隱藏消息,就能達到這個目的。在往表單中添加新英雄時,將看到這種選擇的重要性。

Alter Ego 是可選項,所以不用改它。

英雄的超能力選項是必填的。只要愿意,可以往<select>上添加相同的錯誤處理。但沒有必要,這個選擇框已經限制了“超能力”只能選有效值。

添加清除按鈕

在組件類中添加clear()方法:

// lib/src/hero_form_component.dart (clear)

void clear() {
  model.name = '';
  model.power = _powers[0];
  model.alterEgo = '';
}

提交 按鈕的右邊添加一個綁定click事件的 Clear 按鈕:

// lib/src/hero_form_component.html (Clear button)

<button (click)="clear()" type="button" class="btn">
  Clear
</button>

刷新瀏覽器。點擊 Clear 按鈕。文本域都被清空,如果之前改變了Power 的值,也會重置為默認值。

使用 ngSubmit 提交表單

在填表完成之后,用戶應該能提交這個表單。Submit 按鈕位于表單的底部,它自己不做任何事,但因為它的 type 值 (type="submit"),所以會觸發表單提交。

此時提交表單是無意義的。為了讓它有意義,指定表單組件的onSubmit()方法,綁定到表單的ngSubmit事件綁定:

<form (ngSubmit)="onSubmit()" #heroForm="ngForm">

注意模板引用變量#heroForm。正如前面所說的,變量heroForm被綁定到管理整個表單的NgForm指令。

NgForm指令

Angular 自動創建并添加 NgForm 指令到<form>標簽。

NgForm指令給表單元素附加了額外的特性。它會控制那些帶有ngModelngControl指令的控件元素,監聽他們的屬性,包括其有效性。

你要把表單的總體有效性通過heroForm變量綁定到按鈕的disabled屬性上。

<button [disabled]="!heroForm.form.valid" type="submit" class="btn btn-primary">
  Submit
</button>

刷新瀏覽器。你會發現按鈕是可用的——盡管它還沒有做任何有用的事。

現在如果我們刪除 Name,就違反了“required”規則,它會恰當的顯示在錯誤信息中。提交 按鈕也被禁用了。

不覺得了不起嗎?再想一想。如果沒有 Angular 的幫助,我們又該怎樣讓按鈕的禁用/啟用狀態和表單的有效性關聯起來呢?

有了 Angular,它就是這么簡單:

  1. 在(增強的)form 元素上定義一個模板引用變量。
  2. 在許多行之外的按鈕上引用該變量。

顯示模型 (可選)

此時提交表單沒有視覺特效。

改進 demo 也無法教給我們任何關于表單的新知識。但這是一個練習新學到的綁定技能的好機會。如果你不感興趣,跳到本章的總結部分。

作為視覺效果,隱藏掉數據輸入區域并顯示一些其它東西。

把表單包裹進<div>中,并把它的hidden屬性綁定到HeroFormComponent.submitted屬性。

// lib/src/hero_form_component.html (excerpt)

  <div [hidden]="submitted">
    <h1>Hero Form</h1>
    <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
    </form>
  </div>

表單從一開始就是可見的,因為submitted屬性是 false,直到我們提交了這個表單,正如下面這段HeroFormComponent的代碼展示的:

// lib/src/hero_form_component.dart (submitted)

bool submitted = false;

void onSubmit() => submitted = true;

現在,在剛寫的<div>包裹層下方,添加如下 HTML:

// lib/src/hero_form_component.html (submitted)

<div [hidden]="!submitted">
  <h1>Hero data</h1>

  <table class="table">
    <tr>
      <th>Name</th>
      <td>{{model.name}}</td>
    </tr>
    <tr>
      <th>Alter Ego</th>
      <td>{{model.alterEgo}}</td>
    </tr>
    <tr>
      <th>Power</th>
      <td>{{model.power}}</td>
    </tr>
  </table>

  <button (click)="submitted=false" class="btn btn-primary">Edit</button>
</div>

刷新瀏覽器,并提交表單。submitted標記變成 true,表單消失了。你會看到英雄模型的值(只讀)顯示在表格中。

這個視圖包含一個 Edit 按鈕,它的點擊事件綁定清除submitted標志。當你點擊 Edit 按鈕時,表格消失,可編輯的表單又出現了。

總結

Angular 表單提供了數據修改、驗證等支持。在本章中學到了怎樣使用下面的特性:

  • 一個 HTML 表單模板和一個帶有@Component注解的表單組件類。
  • 通過一個ngSubmit事件綁定處理表單提交。
  • 模板引用變量,例如heroFormname
  • 雙向數據綁定([(ngModel)])。
  • 用于驗證和追蹤表單元素變化的ngControl指令。
  • input 控件的valid屬性(通過模板引用變量獲取),用于檢查控件的有效性和顯示/隱藏錯誤信息。
  • NgForm.form的有效性來設置 提交 按鈕的激活狀態。
  • 定制 CSS 類來給用戶提供控件狀態的視覺反饋。

下一步

依賴注入

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • angular提供了模板驅動和模型驅動兩種方式來構建表單。模板驅動模式使用模板表單內置指令、內置校驗的方式來構建表...
    oWSQo閱讀 892評論 0 0
  • 表單創建一個有機、有效、引人注目的數據輸入體驗。 Angular 表單協調一組數據綁定控件,跟蹤變更,驗證輸入的有...
    趙然228閱讀 1,508評論 0 0
  • 細說 Angular 2+ 的表單(二):響應式表單 摘要 在企業應用開發時,表單是一個躲不過去的事情,和面向消費...
    接灰的電子產品閱讀 5,842評論 8 28
  • 主要內容 第一節 - 創建最簡單的輸入框 第二節 - 添加簡單的驗證功能 第三節 - 顯示驗證失敗的錯誤信息 第四...
    semlinker閱讀 1,245評論 0 4
  • 周末閑在家里,一早山上透氣, 順手摘點杏兒,品嘗人間美味。 不想與人為伍,江湖實在太亂, 沒事看看格斗,生活真是愜意。
    健行賤遠閱讀 233評論 0 1