2019獨角獸企業重金招聘Python工程師標準>>>
表單是商業應用程序的主流。您可以使用表單登錄,提交幫助請求,下訂單,預訂航班,安排會議,并執行無數其他數據錄入任務。
在開發表單時,創建一個數據錄入體驗非常重要,該體驗可以通過工作流高效地引導用戶。
開發表單需要設計技巧(超出本頁面的范圍),以及雙向數據綁定,更改跟蹤,驗證和錯誤處理的框架支持,您將在本頁面上了解這些信息。
本頁面向您展示了如何從頭構建一個簡單的表單。一路上你將學習如何:
- 用組件和模板構建一個Angular表單。
- 使用ngModel創建讀取和寫入輸入控制值的雙向數據綁定。
- 跟蹤狀態變化和表單控件的有效性。
- 使用跟蹤控件狀態的特殊CSS類提供視覺反饋。
- 向用戶顯示驗證錯誤并啟用/禁用表單控件。
- 使用模板引用變量在HTML元素之間共享信息。
您可以在Plunker中運行實例(查看源代碼)并從那里下載代碼。
模板驅動的形式
您可以通過使用本頁中描述的特定于表單的指令和技術在Angular模板語法中編寫模板來構建表單。
您也可以使用響應式(或模型驅動)方法來構建表單。 但是,此頁面重點介紹模板驅動的表單。
您可以使用Angular模板 構建幾乎任何表單- 登錄表單,聯系表單和幾乎任何業務表單。 您可以創造性地設計控件,將它們綁定到數據,指定驗證規則和顯示驗證錯誤,有條件地啟用或禁用特定控件,觸發內置的視覺反饋等等。
Angular通過許多重復的,模板化的任務使處理過程變得簡單。
您將學習如何構建一個模板驅動的表單,如下所示:
英雄就業機構使用這種形式來維護關于英雄的個人信息。 每個英雄都需要一份工作。 讓正確的英雄與正確的危機相匹配是公司的使命。
這個表格中的三個字段中的兩個是必需的。 遵循材料設計準則,必填字段帶有星號(*)。
如果您刪除了英雄名稱,表單將以吸引人注意的風格顯示驗證錯誤:
請注意提交按鈕被禁用,并且輸入控件從綠色變為紅色。
您將以小步驟構建此表單:
- 創建英雄模型類。
- 創建控制表單的組件。
- 用初始表單布局創建一個模板。
- 使用ngModel雙向數據綁定語法將數據屬性綁定到每個表單控件。
- 為每個表單輸入控件添加一個ngControl指令。
- 添加自定義CSS來提供視覺反饋。
- 顯示和隱藏驗證錯誤消息。
- 使用ngSubmit處理表單提交。
- 禁用窗體的提交按鈕,直到窗體有效。
建立
按照設置說明創建一個名為表單的新項目。
添加angular_forms
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概念。
- 代碼導入您剛創建的主Angular庫和Hero模型。
- hero-form的@Component選擇器值意味著您可以使用<hero-form>元素將此表單放在父模板中。
- templateUrl屬性指向模板HTML的單獨文件(您將很快創建)。
- 您為model和power定義了模擬數據。
順便說一句,您可以注入數據服務來獲取和保存真實數據,或者將這些屬性作為輸入和輸出(請參閱“模板語法”頁面中的輸入和輸出屬性)來綁定到父組件。 這不是現在的問題,這些未來的變化不會影響表單。
修改應用程序組件
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">
刷新瀏覽器。 你會看到一個樣式化的表單!
使用* ngFor添加powers
英雄必須從一個固定的機構批準的權力列表中選擇一個超級大國。 您在內部維護該列表(在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; 您使用插值語法顯示其名稱。
與ngModel的雙向數據綁定
現在運行應用程序有點令人失望。
你沒有看到英雄數據,因為你還沒有綁定到英雄。 你知道如何從早期的頁面做到這一點。 顯示數據教導屬性綁定。 用戶輸入顯示如何使用事件綁定監聽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)都跟蹤自己的狀態,并通過以下字段成員使狀態可供檢查:
- dirty和pristine表明控制的值是否已經改變。
- touched和untouched指示控件是否被訪問過。
- valid反映了控制值的有效性。
樣式控件
有效的控制屬性是最有趣的,因為當一個控制值無效時,你想發送一個強烈的視覺信號。 要創建這樣的視覺反饋,您將使用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.刪除名稱。
- 輸入框邊框變為紅色。
- 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};
}
使用此方法返回的映射值綁定到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,它將恢復為默認值。
用ngSubmit提交表單
用戶應該能夠在填寫表單后提交這個表單。表單底部的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的幫助,你需要做什么才能將按鈕的啟用/禁用狀態連接到表單的有效性?
對你來說,這很簡單:
- 在(增強的)表單元素上定義一個模板引用變量。
- 在多處的按鈕中引用該變量。
顯示Model(可選)
提交表單目前沒有視覺效果。
如預期的演示。 增加代碼過后的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表單為數據修改,驗證等提供支持。 在此頁面中,您學習了如何使用以下功能:
- 一個HTML表單模板和一個帶有@Component注解的表單組件類。
- 表單提交,通過ngSubmit事件綁定處理。
- 模板引用變量,如heroForm和name。
- 雙向數據綁定([(ngModel)])。
- 用于驗證和表單元素更改跟蹤的NgControl?指令。
- 輸入控件(通過模板引用變量訪問)的valid?屬性,用于檢查控件有效性以及顯示/隱藏錯誤消息。
- NgForm.form的有效性來設置提交按鈕的啟用狀態。
- 自定義CSS類為用戶提供有關控制狀態的可視反饋。
最終的項目文件夾結構應該如下所示:
以下是應用程序最終版本的代碼:
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);
}
?