表單是商業應用程序的主流。您可使用表單登陸,提交幫助請求,下訂單,預訂航班,安排會議,並執行無數其餘數據錄入任務。css
在開發表單時,建立一個數據錄入體驗很是重要,該體驗能夠經過工做流高效地引導用戶。html
開發表單須要設計技巧(超出本頁面的範圍),以及雙向數據綁定,更改跟蹤,驗證和錯誤處理的框架支持,您將在本頁面上了解這些信息。java
本頁面向您展現瞭如何從頭構建一個簡單的表單。一路上你將學習如何:git
您能夠在Plunker中運行實例(查看源代碼)並從那裏下載代碼。github
您能夠經過使用本頁中描述的特定於表單的指令和技術在Angular模板語法中編寫模板來構建表單。web
您也可使用響應式(或模型驅動)方法來構建表單。 可是,此頁面重點介紹模板驅動的表單。編程
您可使用Angular模板 構建幾乎任何表單- 登陸表單,聯繫表單和幾乎任何業務表單。 您能夠創造性地設計控件,將它們綁定到數據,指定驗證規則和顯示驗證錯誤,有條件地啓用或禁用特定控件,觸發內置的視覺反饋等等。bootstrap
Angular經過許多重複的,模板化的任務使處理過程變得簡單。api
您將學習如何構建一個模板驅動的表單,以下所示:瀏覽器
英雄就業機構使用這種形式來維護關於英雄的我的信息。 每一個英雄都須要一份工做。 讓正確的英雄與正確的危機相匹配是公司的使命。
這個表格中的三個字段中的兩個是必需的。 遵循材料設計準則,必填字段帶有星號(*)。
若是您刪除了英雄名稱,表單將以吸引人注意的風格顯示驗證錯誤:
請注意提交按鈕被禁用,而且輸入控件從綠色變爲紅色。
您將以小步驟構建此表單:
按照設置說明建立一個名爲表單的新項目。
Angular表單功能位於angular_forms庫中,該庫位於其本身的包中。 將該包添加到pubspec依賴項:
當用戶輸入表單數據時,您將捕獲其更改並更新模型的實例。 直到你知道模型是什麼樣子,你才能佈置表格。
一個模型能夠像「錢包」同樣簡單,掌握關於應用程序重要事實的事實。 這很好地描述了英雄類與三個必填字段(id, name, power)和一個可選字段(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 = const [ 'Really Smart', 'Super Flexible', 'Super Hot', 'Weather Changer' ]; @Component( selector: 'hero-form', templateUrl: 'hero_form_component.html', directives: const [CORE_DIRECTIVES, 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概念。
順便說一句,您能夠注入數據服務來獲取和保存真實數據,或者將這些屬性做爲輸入和輸出(請參閱「模板語法」頁面中的輸入和輸出屬性)來綁定到父組件。 這不是如今的問題,這些將來的變化不會影響表單。
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: const [HeroFormComponent], ) class AppComponent {}
使用如下內容建立模板文件:lib/src/hero_form_component.html (start)
<div class="container"> <h1>Hero 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="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">* Required</small> </div> </form> </div>
該語言只是HTML5。 您將展現兩個Hero字段,name和alterEgo,並在輸入框中將其打開以供用戶輸入。
Name <input>控件具備HTML5必需屬性; Alter Ego <input>控件什麼也不作,由於alterEgo是可選的。
您在底部添加了一個提交按鈕,其中有一些類用於樣式。
你尚未使用Angular。 沒有綁定或額外的指令,只是佈局。
在模板驅動的表單中,若是已經導入了angular_forms庫,則沒必要爲了使用庫功能而對<form>標記執行任何操做。 繼續看看這是如何工做的。
刷新瀏覽器。 你會看到一個簡單的,沒有樣式的表單。
通常的CSS類container和btn來自Bootstrap。 Bootstrap還具備form-specific的類,包括form-control和form-group。 一塊兒,這些給表單了一些樣式。
Angular可不使用Bootstrap類或任何外部庫的樣式。 Angular的應用程序可使用任何CSS庫或不使用。
經過將如下連接插入到index.html的<head>中來添加Bootstrap樣式:web/index.html (bootstrap)
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
刷新瀏覽器。 你會看到一個樣式化的表單!
英雄必須從一個固定的機構批准的權力列表中選擇一個超級大國。 您在內部維護該列表(在HeroFormComponent中)。
您將在表單中添加一個select,並使用ngFor(先前在「顯示數據」頁面中看到的一種技術)將選項綁定到powers列表。
在Alter Ego group下方添加如下HTML:lib/src/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 p of powers" [value]="p">{{p}}</option> </select> </div>
這段代碼重複列表中每一個power 的<option>標籤。 p模板輸入變量在每次迭代中是不一樣的power; 您使用插值語法顯示其名稱。
如今運行應用程序有點使人失望。
你沒有看到英雄數據,由於你尚未綁定到英雄。 你知道如何從早期的頁面作到這一點。 顯示數據教導屬性綁定。 用戶輸入顯示如何使用事件綁定監聽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 *</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" ngControl="name"> </div>
你在form-group以前添加了一個診斷插值,因此你能夠看到你在作什麼。 當你完成的時候,你留下一張紙條扔掉它。
關注綁定語法:[(ngModel)] =「...」。
如今運行應用程序並輸入名稱輸入,添加和刪除字符。 您會看到這些字符出如今診斷文本中並消失。 在某個時候,它可能看起來像這樣:
診斷結果代表數值確實是從輸入流向模型,再返回。
這是雙向的數據綁定。 有關更多信息,請參見模板語法頁面上的與NgModel的雙向綁定。
請注意,您還爲<input>標記添加了一個ngControl指令,並將其設置爲「name」,這對於英雄的名字是有意義的。 任何惟一值將會這樣作,但使用描述性名稱是有幫助的。 將[(ngModel)]與表單結合使用時,定義ngControl指令是一項要求。
在內部,Angular建立NgFormControl實例,並使用Angular附加到<form>標籤的NgForm指令註冊它們。 每一個NgFormControl都是在您分配給ngControl指令的名稱下注冊的。 本指南稍後將詳細介紹NgForm。
在Alter Ego和Hero Power上添加相似的[(ngModel)]綁定和ngControl指令。
用model替換診斷綁定表達式。 經過這種方式,您能夠確認雙向數據綁定適用於整個英雄模型。
修改後,表單的核心應該是這樣的: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 *</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 *</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屬性使用它來匹配label和input控件。
- 每一個input元素都有一個ngControl指令,Angular表單須要用這個指令在表單上註冊控件。
若是您如今運行應用程序並更改每一個英雄model屬性,表單可能會顯示以下:
靠近表單頂部的診斷確認全部的更改都反映在model中。
從模板中刪除診斷綁定,由於它已經達到了目的。
使用CSS和類綁定,您能夠更改表單控件的外觀以反映其狀態。
Angular表單控件能夠告訴您用戶是否觸摸了該控件,值是否改變,或者該值是否失效。
每一個Angular控制(NgControl)都跟蹤本身的狀態,並經過如下字段成員使狀態可供檢查:
有效的控制屬性是最有趣的,由於當一個控制值無效時,你想發送一個強烈的視覺信號。 要建立這樣的視覺反饋,您將使用Bootstrap自定義表單類 is-valid和is-invalid。
將名爲name的模板引用變量添加到Name <input>標記中。 使用name和類綁定來有條件地分配適當的表單有效性類。
臨時將另外一個名爲spy的模板引用變量添加到Name <input>標記,並使用它顯示輸入的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.看看名字輸入。
2.經過添加一些字符來更改name。 類保持不變。
3.刪除名稱。
刪除#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}; }
使用此方法返回的映射值綁定到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 <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 輸入。 顯示錯誤消息。
您能夠經過根據名稱控制的狀態設置<div>的隱藏屬性來控制錯誤消息的可見性。
在這個例子中,當控件是有效的或者原始的時候隱藏消息 - 「pristine」意味着用戶沒有改變這個值,由於它是以這種形式顯示的。
用戶體驗是開發者的選擇
有些開發人員但願消息始終顯示。 若是您忽略原始狀態,則只有在該值有效時纔會隱藏該消息。 若是您使用新(空白)英雄或無效英雄到達此組件,則在您執行任何操做以前,您將當即看到錯誤消息。
有些開發人員但願僅在用戶進行無效更改時顯示消息。 當控件是「原始的」時隱藏消息實現了這個目標。 當您向表單添加一個「清除」按鈕時,您會看到此選項的重要性。
英雄Alter Ego是可選的,因此你能夠不用關那個。
英雄power選擇是必需的。 若是須要,能夠將相同類型的錯誤消息添加到<select>中,但這不是必須的,由於選擇框已經將權限限制爲有效值。
將clear()方法添加到組件類中:lib/src/hero_form_component.dart (clear)
void clear() { model.name = ''; model.power = _powers[0]; model.alterEgo = ''; }
在提交按鈕後面添加一個帶有點擊事件綁定的清除按鈕:lib/src/hero_form_component.html (Clear button)
<button (click)="clear()" type="button" class="btn"> Clear </button>
刷新瀏覽器。 點擊清除按鈕。 文本字段變爲空白,若是您更改了power,它將恢復爲默認值。
用戶應該可以在填寫表單後提交這個表單。表單底部的Submit按鈕自己不作任何事情,可是因爲它的類型(type =「submit」),它會觸發一個表單提交。
表單提交目前是無用的。 爲了使它有用,將表單組件的onSubmit()方法分配給表單的ngSubmit事件綁定:
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
請注意模板引用變量#heroForm。 正如前面所解釋的,變量heroForm被綁定到總體管理表單的NgForm指令。
NgForm指令
Angular自動建立並附加一個NgForm指令給<form>標籤。
NgForm指令補充表單元素的附加功能。 它包含用ngModel和ngControl指令爲元素建立的控件,並監視它們的屬性,包括它們的有效性。
您將經過heroForm變量將表單的總體有效性綁定到按鈕的disabled屬性:
<button [disabled]="!heroForm.form.valid" type="submit" class="btn btn-primary"> Submit </button>
刷新瀏覽器。 你會發現這個按鈕是啓用的,儘管它沒有作任何有用的事情。
如今,若是您刪除Name,則違反了「必需的」規則,這在錯誤消息中正確記錄。 提交按鈕也被禁用。
沒有留下深入印象? 想想。 若是沒有Angular的幫助,你須要作什麼才能將按鈕的啓用/禁用狀態鏈接到表單的有效性?
對你來講,這很簡單:
提交表單目前沒有視覺效果。
如預期的演示。 增長代碼事後的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>
該表單從一開始就是可見的,由於在提交表單以前,提交的屬性爲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>
刷新瀏覽器並提交表單。 提交的標誌變爲真,表格消失。 您將看到表格中顯示的英雄模型值(只讀)。
該視圖包含一個編輯按鈕,其單擊事件綁定將清除提交的標誌。 當您單擊編輯按鈕時,該表消失,而且可編輯的表單從新出現。
Angular表單爲數據修改,驗證等提供支持。 在此頁面中,您學習瞭如何使用如下功能:
最終的項目文件夾結構應該以下所示:
如下是應用程序最終版本的代碼:
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: const [HeroFormComponent], ) class AppComponent {}
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'; }
lib/src/hero_form_component.dart
import 'package:angular/angular.dart'; import 'package:angular_forms/angular_forms.dart'; import 'hero.dart'; const List<String> _powers = const [ 'Really Smart', 'Super Flexible', 'Super Hot', 'Weather Changer' ]; @Component( selector: 'hero-form', templateUrl: 'hero_form_component.html', directives: const [CORE_DIRECTIVES, 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; /// Returns a map of CSS class names representing the state of [control]. Map<String, bool> setCssValidityClass(NgControl control) { final validityClass = control.valid == true ? 'is-valid' : 'is-invalid'; return {validityClass: true}; } void clear() { model.name = ''; model.power = _powers[0]; model.alterEgo = ''; } }
lib/src/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="ngForm" [class.is-valid]="name.valid" [class.is-invalid]="!name.valid" ngControl="name"> <div [hidden]="name.valid || name.pristine" class="invalid-feedback"> 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" ngControl="alterEgo"> </div> <div class="form-group"> <label for="power">Hero Power *</label> <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> </div> <div class="row"> <div class="col-auto"> <button [disabled]="!heroForm.form.valid" type="submit" class="btn btn-primary"> Submit </button> <button (click)="clear()" type="button" class="btn"> Clear </button> </div> <small class="col text-right">* Required</small> </div> </form> </div> <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> </div>
web/index.html
<!DOCTYPE html> <html> <head> <script> // WARNING: DO NOT set the <base href> like this in production! // Details: https://webdev.dartlang.org/angular/guide/router (function () { var m = document.location.pathname.match(/^(\/[-\w]+)+\/web($|\/)/); document.write('<base href="' + (m ? m[0] : '/') + '" />'); }()); </script> <title>Hero Form</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> <link rel="stylesheet" href="styles.css"> <link rel="icon" type="image/png" href="favicon.png"> <script defer src="main.dart" type="application/dart"></script> <script defer src="packages/browser/dart.js"></script> </head> <body> <my-app>Loading ...</my-app> </body> </html>
web/main.dart
import 'package:angular/angular.dart'; import 'package:forms/app_component.dart'; void main() { bootstrap(AppComponent); }