本教程將為你演示如何創(chuàng)建一個模板驅(qū)動表單,它的控件元素綁定到數(shù)據(jù)屬性,并通過輸入驗證來保持數(shù)據(jù)的完整性和樣式,以改善用戶體驗。
當在模板中進行更改時,模板驅(qū)動表單會使用雙向數(shù)據(jù)綁定來更新組件中的數(shù)據(jù)模型,反之亦然。
Angular 支持兩種交互式表單的設(shè)計方法。你可以使用 Angular 中的模板語法和指令,以及本教程中描述的表單專用指令和技巧編寫模板來構(gòu)建表單,或者你可以使用響應(yīng)式方式(或叫模型驅(qū)動方式)來構(gòu)建表單。
模板驅(qū)動表單適用于小型或簡單的表單,而響應(yīng)式表單則更具伸縮性,適用于復(fù)雜表單。
你可以用 Angular 模板來構(gòu)建各種表單,比如登錄表單、聯(lián)系人表單和幾乎所有的業(yè)務(wù)表單。你可以創(chuàng)造性地對控件進行布局并把它們綁定到對象模型的數(shù)據(jù)上。你可以指定驗證規(guī)則并顯示驗證錯誤,有條不紊地啟用或禁用特定控件,觸發(fā)內(nèi)置的視覺反饋等等。
本教程將向你展示如何通過一個簡化的范例表單來從頭構(gòu)建一個表單,就像“英雄之旅”教程的中用一個表單來講解這些技巧一樣。
運行或下載范例應(yīng)用:現(xiàn)場演練 / 下載范例。
本教程將教你如何執(zhí)行以下操作:
ngModel
?創(chuàng)建雙向數(shù)據(jù)綁定,以便讀寫輸入控件的值模板驅(qū)動表單依賴于 ?FormsModule
?定義的指令。
指令 |
詳細信息 |
---|---|
NgModel
|
會協(xié)調(diào)其附著在的表單元素中的值變更與數(shù)據(jù)模型中的變更,以便你通過輸入驗證和錯誤處理來響應(yīng)用戶輸入。 |
NgForm
|
會創(chuàng)建一個頂級的 |
NgModelGroup
|
會創(chuàng)建 |
英雄雇傭管理局使用本指南中的范例表單來維護英雄的個人信息。畢竟英雄也要工作啊。這個表單有助于該機構(gòu)將正確的英雄與正確的危機匹配起來。
該表單突出了一些易于使用的設(shè)計特性。比如,這兩個必填字段的左邊是綠色條,以便讓它們醒目。這些字段都有初始值,所以表單是有效的,并且 Submit 按鈕也是啟用的。
當你使用這個表單時,你將學(xué)習如何包含驗證邏輯,如何使用標準 CSS 自定義表達式,以及如何處理錯誤條件以確保輸入的有效性。比如,如果用戶刪除了英雄的名字,那么表單就會失效。該應(yīng)用會檢測已更改的狀態(tài),并以醒目的樣式顯示驗證錯誤。此外,Submit 按鈕會被禁用,輸入控件左側(cè)的“必填”欄也會從綠色變?yōu)榧t色。
在本教程中,你將使用以下步驟將一個范例表單綁定到數(shù)據(jù)并處理用戶輸入。
FormsModule
?ngModel
?指令和雙向數(shù)據(jù)綁定語法把表單控件綁定到數(shù)據(jù)屬性。ngModel
?如何使用 CSS 類報告控件狀態(tài)ngModel
?可以訪問它們ngModel
?跟蹤輸入的有效性和控件的狀態(tài)。ngSubmit
?輸出屬性來處理表單提交。你可以根據(jù)這里提供的代碼從頭創(chuàng)建范例應(yīng)用,也可以查看 現(xiàn)場演練 / 下載范例。
Hero
?類,用于定義表單中所反映的數(shù)據(jù)模型。export class Hero {
constructor(
public id: number,
public name: string,
public power: string,
public alterEgo?: string
) { }
}
HeroFormComponent
? 類中定義的。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>
? 標簽把這個表單放到父模板中。
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"
這個演示使用虛擬數(shù)據(jù)來表達 ?model
?和 ?powers
?。在真正的應(yīng)用中,你會注入一個數(shù)據(jù)服務(wù)來獲取和保存實際數(shù)據(jù),或者把它們作為輸入屬性和輸出屬性進行公開。
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-hero-form></app-hero-form>
初始模板定義了一個帶有兩個表單組和一個提交按鈕的表單布局。表單組對應(yīng)于 Hero 數(shù)據(jù)模型的兩個屬性:name 和 alterEgo。每個組都有一個標簽和一個用戶輸入框。
<input>
? 控件元素中包含了 HTML5 的 ?required
?屬性<input>
? 沒有控件元素,因為 ?alterEgo
?是可選的Submit 按鈕里面有一些用于樣式化的類。此時,表單布局全都是純 HTML5,沒有綁定或指令。
container
?,?form-group
?,?form-control
? 和 ?btn
?。要使用這些樣式,就要在該應(yīng)用的樣式表中導(dǎo)入該庫。@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
powers
?列表是數(shù)據(jù)模型的一部分,在 ?HeroFormComponent
?內(nèi)部維護。Angular 的?NgForOf
?指令會遍歷這些數(shù)據(jù)值,以填充這個 ?<select>
? 元素。<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īng)用,你會看到選擇控件中的超能力列表。由于尚未將這些 input 元素綁定到數(shù)據(jù)值或事件,因此它們?nèi)匀皇强瞻椎模瑳]有任何行為。
下一步是使用雙向數(shù)據(jù)綁定把輸入控件綁定到相應(yīng)的 ?Hero
?屬性,這樣它們就可以通過更新數(shù)據(jù)模型來響應(yīng)用戶的輸入,并通過更新顯示來響應(yīng)數(shù)據(jù)中的程序化變更。
該 ?ngModel
?指令是由 ?FormsModule
?聲明的,它能讓你把模板驅(qū)動表單中的控件綁定到數(shù)據(jù)模型中的屬性。當你使用雙向數(shù)據(jù)綁定的語法 ?[(ngModel)]
? 引入該指令時,Angular 就可以跟蹤控件的值和用戶交互,并保持視圖與模型的同步。
hero-form.component.html
?。<input>
? 標記。[(ngModel)]="..."
? 添加 ?ngModel
?指令。<input type="text" class="form-control" id="name"
required
[(ngModel)]="model.name" name="name">
TODO: remove this: {{model.name}}
這個例子中在每個 input 標記后面都有一個臨時的診斷插值 ?
{{model.name}}
?,以顯示相應(yīng)屬性的當前數(shù)據(jù)值。本提醒是為了讓你在觀察完這個雙向數(shù)據(jù)綁定后刪除這些診斷行。
當你導(dǎo)入了 ?FormsModule
?時,Angular 會自動為模板中的 ?<form>
? 標簽創(chuàng)建并附加一個 ?NgForm
?指令。(因為 ?NgForm
?定義了一個能匹配 ?<form>
? 元素的選擇器 ?form
?)。
要訪問 ?NgForm
?和表單的整體狀態(tài),就要聲明一個模板引用變量。
hero-form.component.html
?。<form>
? 標簽添加模板引用變量 ?#heroForm
?,并把它的值設(shè)置如下。<form #heroForm="ngForm">
模板變量 ?heroForm
?現(xiàn)在是對 ?NgForm
?指令實例的引用,該指令實例管理整個表單。
在添加和刪除字符時,你可以看到它們從數(shù)據(jù)模型中出現(xiàn)和消失。比如:
用來顯示插值的診斷行證明了這些值確實從輸入框流向了模型,然后再返回。
在元素上使用 ?[(ngModel)]
? 時,必須為該元素定義一個 ?name
?屬性。Angular 會用這個指定的名字來把這個元素注冊到父 ?<form>
? 元素上的 ?NgForm
?指令中。
這個例子中為 ?<input>
? 元素添加了一個 ?name
?屬性,并把它的值設(shè)置為 “name”,用來表示英雄的名字。任何唯一的值都可以用,但最好用描述性的名稱。
[(ngModel)]
? 綁定和 ?name
?屬性。json
?管道的新文本綁定。?json
?管道會把數(shù)據(jù)序列化為字符串。表單模板修改完畢后,應(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>
<input>
? 元素都有一個 ?id
?屬性。?<label>
? 元素的 ?for
?屬性用它來把標簽匹配到輸入控件。這是一個標準的 HTML 特性。<input>
? 元素都有一個必需的 ?name
?屬性,Angular 用它來注冊表單中的控件。如果你現(xiàn)在運行該應(yīng)用并更改英雄模型的每個屬性,該表單可能會顯示如下:
通過表單頂部的診斷行可以確認所有的更改都已反映在模型中。
{{ model | json }}
? 的文本綁定了。控件上的 ?NgModel
?指令會跟蹤該控件的狀態(tài)。它會告訴你用戶是否接觸過該控件、該值是否發(fā)生了變化,或者該值是否無效。Angular 在控件元素上設(shè)置了特殊的 CSS 類來反映其狀態(tài),如下表所示。
狀態(tài) |
為 TRUE 時的類名 |
為 FALSE 時的類名 |
---|---|---|
該控件已被訪問過。 |
ng-touched
|
ng-untouched
|
控件的值已被更改。 |
ng-dirty
|
ng-pristine
|
控件的值是有效的。 |
ng-valid
|
ng-invalid
|
此外,Angular 還會在提交時把 ?ng-submitted
? 類應(yīng)用到 ?<form>
? 元素上。這個類不會應(yīng)用到內(nèi)部控件上。
你可以用這些 CSS 類來根據(jù)控件的狀態(tài)定義其樣式。
要想知道框架是如何添加和移除這些類的,請打開瀏覽器的開發(fā)者工具,檢查代表英雄名字的 ?<input>
?
<input>
? 元素。除了 “form-control” 類之外,你還可以看到該元素有多個 CSS 類。<input … class="form-control ng-untouched ng-pristine ng-valid" …>
<input>
? 框中執(zhí)行以下操作,看看會出現(xiàn)哪些類。ng-touched
? 類,取代了 ?ng-untouched
? 類。ng-invalid
? 類會取代 ?ng-valid
? 類。注意 ?ng-valid
? / ?ng-invalid
? 這兩個類,因為你想在值無效時發(fā)出強烈的視覺信號。你還要標記必填字段。
你可以在輸入框的左側(cè)用彩條標記必填字段和無效數(shù)據(jù):
要想用這種方式修改外觀,請執(zhí)行以下步驟。
ng-*
? CSS 類添加一些定義。forms.css
? 文件中。index.html
? 的兄弟:.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid:not(form) {
border-left: 5px solid #a94442; /* red */
}
index.html
? 文件中,更新 ?<head>
? 標簽以包含新的樣式表。<link rel="stylesheet" href="assets/forms.css">
Name 輸入框是必填的,清除它就會把彩條變成紅色。這表明有些東西是錯的,但是用戶并不知道要怎么做或該做什么。你可以通過查看和響應(yīng)控件的狀態(tài)來提供有用的信息。
當用戶刪除該名字時,該表單應(yīng)如下所示:
Hero Power 選擇框也是必填的,但它不需要這樣的錯誤處理,因為選擇框已經(jīng)把選擇限制在有效值范圍內(nèi)。
要在適當?shù)臅r候定義和顯示錯誤信息,請執(zhí)行以下步驟。
<input>
? 標簽,你可以用來從模板中訪問輸入框的 Angular 控件。在這個例子中,該變量是 ?#name="ngModel"
?。模板引用變量(?
#name
?)設(shè)置為 ?"ngModel"
?,因為 "ngModel" 是 ?NgModel.exportAs
? 屬性的值。這個屬性告訴 Angular 如何把引用變量和指令鏈接起來。
<div>
?name
?控件的屬性綁定到 ?<div>
? 元素的 ?hidden
?屬性來顯示或隱藏錯誤信息。<div [hidden]="name.valid || name.pristine"
class="alert alert-danger">
name
?輸入框添加一個有條件的錯誤信息,如下例所示。<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)的說明
在這個例子中,當控件是有效的(valid)或者是原始的(pristine)時,你會隱藏這些消息。原始表示該用戶在此表單中顯示的值尚未更改過。如果你忽略了 ?pristine
?狀態(tài),那么只有當值有效時才會隱藏這些消息。如果你把一個新的(空白)英雄或一個無效的英雄傳給這個組件,你會立刻看到錯誤信息,而這時候你還沒有做過任何事情。
你可能希望只有在用戶做出無效更改時,才顯示該消息。因此當 ?pristine
?狀態(tài)時,隱藏這條消息就可以滿足這個目標。當你在下一步中為表單添加一個新的英雄時,就會看到這個選擇有多重要。
本練習通過添加模型數(shù)據(jù),展示了如何響應(yīng)原生 HTML 按鈕單擊事件。要讓表單用戶添加一個新的英雄,就要添加一個能響應(yīng) click 事件的 New Hero 按鈕。
<button>
? 元素放在表單底部。newHero() {
this.model = new Hero(42, '', '');
}
newHero()
? 上。<button type="button" class="btn btn-default" (click)="newHero()">New Hero</button>
表單會清空,輸入框左側(cè)的必填欄會顯示紅色,說明 ?name
?和 ?power
?屬性無效。請注意,錯誤消息是隱藏的。這是因為表單處于原始狀態(tài)。你還沒有改過任何東西。
現(xiàn)在,該應(yīng)用會顯示一條錯誤信息 ?Name is required
?,因為該輸入框不再是原始狀態(tài)。表單會記住你在單擊 New Hero 之前輸入過一個名字。
newHero()
? 方法之后強制調(diào)用表單的 ?reset()
? 方法以清除所有標志。<button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button>
現(xiàn)在單擊 New Hero 會重置表單及其控件標志。
用戶應(yīng)該可以在填寫之后提交這個表單。表單底部的 Submit 按鈕本身沒有任何作用,但由于它的類型(?type="submit"
?),它會觸發(fā)一個表單提交事件。要響應(yīng)此事件,請執(zhí)行以下步驟。
ngSubmit
?事件屬性綁定到一個 hero-form 組件的 ?onSubmit()
? 方法中。<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
#heroForm
? 訪問包含 Submit 按鈕的表單,并創(chuàng)建一個事件綁定。你可以把表示它整體有效性的 form 屬性綁定到 Submit 按鈕的 ?disabled
?屬性上。<button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>
你不必把按鈕的啟用狀態(tài)明確地關(guān)聯(lián)表單的有效性上。當 ?FormsModule
?在增強的表單元素上定義模板引用變量時,會自動執(zhí)行此操作,然后在按鈕控件中引用該變量。
要展示對表單提交的響應(yīng),你可以隱藏數(shù)據(jù)輸入?yún)^(qū)域并就地顯示其它內(nèi)容。
<div>
? 中并把它的 ?hidden
?屬性綁定到 ?HeroFormComponent.submitted
? 屬性上。<div [hidden]="submitted">
<h1>Hero Form</h1>
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
<!-- ... all of the form ... -->
</form>
</div>
submitted
?屬性都是 false,正如 ?HeroFormComponent
?中的這個片段所顯示的:submitted = false;
onSubmit() { this.submitted = true; }
submitted
?標志就變?yōu)?nbsp;?true
?,表單就會消失。<div>
? 包裝器下添加以下 HTML。<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>
?(用于顯示帶插值綁定的只讀英雄)只在組件處于已提交狀態(tài)時才會出現(xiàn)。
另外還顯示了一個 Edit 按鈕,它的 click 事件綁定到了一個清除 ?submitted
?標志的表達式。
本頁討論的 Angular 表單利用了下列框架特性來支持數(shù)據(jù)修改,驗證等工作。
@Component
? 裝飾器的表單組件類NgForm.ngSubmit
? 事件屬性來處理表單提交#heroForm
? 和 ?#name
?[(ngModel)]
? 語法name
?屬性的用途是驗證和表單元素的變更跟蹤valid
? 屬性來檢查控件是否有效,并據(jù)此顯示或隱藏錯誤信息NgForm
?的有效性來控制 Submit 按鈕的啟用狀態(tài)這里是該應(yīng)用最終版本的代碼:
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 type="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 { 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-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 */
}
更多建議: