>用戶輸入
通過$event對象取得用戶輸入<input (keyup)="onKey($event)">
$event的類型
onKey(event: KeyboardEvent) { // with type info
this.values += (<HTMLInputElement>event.target).value + ' | ';
}
$event的類型現(xiàn)在是KeyboardEvent飒货。 不是所有的元素都有value屬性,所以它將target轉(zhuǎn)換為輸入元素鳍寂。 OnKey方法更加清晰的表達了它期望從模板得到什么,以及它是如何解析事件的情龄。
傳入 $event 是靠不住的做法迄汛,還有另一種獲取用戶數(shù)據(jù)的方式:使用 Angular 的模板引用變量捍壤。在標識符前加上井號 (#) 就能聲明一個模板引用變量。
<input #box (keyup)="onKey(box.value)">
<p>{{values}}</p>
onKey(value: string) { this.values += value + ' | '; }
按鍵事件過濾(通過key.enter)
keyup.enter:只有按下回車鍵才被觸發(fā)
<input #box (keyup.enter)="onEnter(box.value)">
<p>{{value}}</p>
onEnter(value: string) { this.value = value; }
失去焦點事件 (blur)
keyup.enter有一個bug就是如果用戶沒有先按回車鍵鞍爱,而是移開了鼠標鹃觉,點擊了頁面中其它地方,輸入框的當前值就會丟失睹逃。通過同時監(jiān)聽輸入框的回車鍵和失去焦點事件來修正這個問題盗扇。
<input #box
(keyup.enter)="update(box.value)"
(blur)="update(box.value)">
<p>{{value}}</p>
update(value: string) { this.value = value; }
>模板驅(qū)動表單
開發(fā)表單需要設(shè)計能力,而框架支持雙向數(shù)據(jù)綁定沉填、變更檢測疗隶、驗證和錯誤處理。
這個頁面演示了如何從草稿構(gòu)建一個簡單的表單翼闹。這個過程中你將學會如何:
用組件和模板構(gòu)建 Angular 表單
用 ngModel 創(chuàng)建雙向數(shù)據(jù)綁定斑鼻,以讀取和寫入輸入控件的值
跟蹤狀態(tài)的變化,并驗證表單控件
使用特殊的 CSS 類來跟蹤控件的狀態(tài)并給出視覺反饋
向用戶顯示驗證錯誤提示猎荠,以及啟用/禁用表單控件
使用模板引用變量在 HTML 元素之間共享信息
通過 ngModel 跟蹤修改狀態(tài)與有效性驗證:
<!--當在表單中使用 [(ngModel)] 時坚弱,必須要定義 name 屬性。-->
<input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #name="ngModel">
<!--當控件是有效的 (valid) 或全新的 (pristine) 時关摇,隱藏消息-->
<div [hidden]="name.valid || name.pristine"
class="alert alert-danger">
Name is required
</div>
<button type="submit" class="btn btn-s
heroForm.reset() 重置表格
>響應(yīng)式表單
1.基礎(chǔ)知識
首先需要在跟模塊引入
import {FormsModule , ReactiveFormsModule} from '@angular/forms';
導出:
imports: [
...
FormsModule,
ReactiveFormsModule
],
基礎(chǔ)的表單類
- AbstractControl是三個具體表單類的抽象基類荒叶。 并為它們提供了一些共同的行為和屬性,其中有些是可觀察對象(Observable)拒垃。
- FormControl 用于跟蹤一個單獨的表單控件的值和有效性狀態(tài)停撞。它對應(yīng)于一個HTML表單控件瓷蛙,比如輸入框和下拉框悼瓮。它是Angular表單中的最小單元。
- FormGroup用于跟蹤一組AbstractControl的實例的值和有效性狀態(tài)艰猬。 該組的屬性中包含了它的子控件横堡。組件中的頂級表單就是一個FormGroup。
- FormArray用于跟蹤AbstractControl實例組成的有序數(shù)組的值和有效性狀態(tài)冠桃。
- FormBuilder 快速構(gòu)建表單命贴,
this.user = new FormGroup({
email: new FormControl('', [Validators.required, Validators.pattern(/[a-zA-Z0-9]/)]),
password: new FormControl('', [Validators.required]),
repeat: new FormControl('', [Validators.required]),
address: new FormGroup({
province: new FormControl(''),
city: new FormControl(''),
area: new FormControl(''),
addr: new FormControl('')
})
});
從上面的代碼中我們可以看到,這里的表單( FormGroup )是由一系列的表單控件( FormControl )構(gòu)成的食听。其實 FormGroup 的構(gòu)造函數(shù)接受的是三個參數(shù): controls(表單控件『數(shù)組』胸蛛,其實不是數(shù)組,是一個類似字典的對象) 樱报、 validator(驗證器) 和 asyncValidator(異步驗證器) 葬项,其中只有 controls 數(shù)組是必須的參數(shù),后兩個都是可選參數(shù)迹蛤。
// FormGroup 的構(gòu)造函數(shù)
constructor(
controls: {
[key: string]: AbstractControl;
},
validator?: ValidatorFn,
asyncValidator?: AsyncValidatorFn
)
我們上面的代碼中就沒有使用驗證器和異步驗證器的可選參數(shù)民珍,而且注意到我們提供 controls 的方式是襟士,一個 key 對應(yīng)一個 FormControl 。比如下面的 key 是 password嚷量,對應(yīng)的值是 new FormControl('', [Validators.required]) 陋桂。這個 key 對應(yīng)的就是模板中的 formControlName 的值,我們模板代碼中設(shè)置了 formControlName="password" 蝶溶,而表單控件會根據(jù)這個 password 的控件名來跟蹤實際的渲染出的表單頁面上的控件(比如 <input formcontrolname="password">)的值和驗證狀態(tài)嗜历。
password = new FormControl('', [Validators.required]);
那么可以看出,這個表單控件的構(gòu)造函數(shù)同樣也接受三個可選參數(shù)抖所,分別是:控件初始值( formState )秸脱、控件驗證器或驗證器數(shù)組( validator )和控件異步驗證器或異步驗證器數(shù)組( asyncValidator )。上面的那行代碼中部蛇,初始值為空字符串摊唇,驗證器是『必選』,而異步驗證器我們沒有提供涯鲁。
// FormControl 的構(gòu)造函數(shù)
constructor(
formState?: any, // 控件初始值
validator?: ValidatorFn | ValidatorFn[], // 控件驗證器或驗證器數(shù)組
asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] // 控件異步驗證器或異步驗證器數(shù)組
)
由此可以看出巷查,響應(yīng)式表單區(qū)別于模板驅(qū)動型表單的的主要特點在于:是由組件類去創(chuàng)建、維護和跟蹤表單的變化抹腿,而不是依賴模板岛请。
那么我們是否在響應(yīng)式表單中還可以使用 ngModel 呢?當然可以警绩,但這樣的話表單的值會在兩個不同的位置存儲了: ngModel 綁定的對象和 FormGroup 崇败,這個在設(shè)計上我們一般是要避免的,也就是說盡管可以這么做肩祥,但我們不建議這么做后室。
上面的表單構(gòu)造起來雖然也不算太麻煩,但是在表單項目逐漸多起來之后還是一個挺麻煩的工作混狠,所以 Angular 提供了一種快捷構(gòu)造表單的方式 -- 使用 FormBuilder岸霹。
// 初始化表單
this.user = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', Validators.required],
repeat: ['', Validators.required],
address: this.fb.group({
province: [],
city: [],
area: [],
addr: []
})
});
使用 FormBuilder 我們可以無需顯式聲明 FormControl 或 FormGroup 。 FormBuilder 提供三種類型的快速構(gòu)造: control , group 和 array 将饺,分別對應(yīng) FormControl, FormGroup 和 FormArray贡避。 我們在表單中最常見的一種是通過 group 來初始化整個表單。上面的例子中予弧,我們可以看到 group 接受一個字典對象作為參數(shù)刮吧,這個字典中的 key 就是這個 FormGroup 中 FormControl 的名字,值是一個數(shù)組掖蛤,數(shù)組中的第一個值是控件的初始值杀捻,第二個是同步驗證器的數(shù)組,第三個是異步驗證器數(shù)組(第三個并未出現(xiàn)在我們的例子中)坠七。這其實已經(jīng)在隱性的使用 FormBuilder.control 了水醋,可以參看下面的 FormBuilder 中的 control 函數(shù)定義旗笔,其實 FormBuilder 利用我們給出的值構(gòu)造了相對應(yīng)的 control :
// FormBuilder 的構(gòu)造函數(shù)
control(
formState: Object,
validator?: ValidatorFn | ValidatorFn[],
asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]
): FormControl;
此外還值得注意的一點是 address 的處理,我們可以清晰的看到 FormBuilder 支持嵌套拄踪,遇到 FormGroup 時僅僅需要再次使用 this.fb.group({...}) 即可蝇恶。這樣我們的表單在擁有大量的表單項時,構(gòu)造起來就方便多了惶桐。
FormArray 的用法
在購物網(wǎng)站經(jīng)常遇到需要維護多個地址撮弧,因為我們有些商品希望送到公司,有些需要送到家里姚糊,還有些給父母采購的需要送到父母那里贿衍。這就是一個典型的 FormArray 可以派上用場的場景。所有的這些地址的結(jié)構(gòu)都是一樣的救恨,有省贸辈、市、區(qū)縣和街道地址肠槽,那么對于處理這樣的場景擎淤,我們來看看在響應(yīng)式表單中怎么做
首先,我們需要把 HTML 模板改造一下秸仙,現(xiàn)在的地址是多項了嘴拢,所以我們需要在原來的地址部分外面再套一層,并且聲明成 formArrayName="addrs"寂纪。 FormArray 顧名思義是一個數(shù)組席吴,所以我們要對這個控件數(shù)組做一個循環(huán),然后讓每個數(shù)組元素是 FormGroup捞蛋,只不過這次我們的 [formGroupName]="i" 是讓 formGroupName 等于該數(shù)組元素的索引孝冒。
<div [formGroup]="user">
<div formArrayName="addrs">
<button (click)="addAddr()">Add</button>
<div *ngFor="let item of user.controls['addrs'].controls; let i = index;">
<div [formGroupName]="i">
...
</div>
</div>
</div>
</div>
首先是無論表單本身還是控件都可以看成是一系列的基于時間維度的數(shù)據(jù)流了,這個數(shù)據(jù)流可以被多個觀察者訂閱和處理襟交,由于 valueChanges 本身是個 Observable迈倍,所以我們就可以利用 RxJS 提供的豐富的操作符伤靠,將一個對數(shù)據(jù)驗證捣域、處理等的完整邏輯清晰的表達出來。
this.user.valueChanges.subscribe(res => {
console.log(res);
});
打印結(jié)果:
>例子
<form [formGroup]="passForm" text-center class="modifyPassForm" margin>
<input padding id="pass" formControlName = "passWord" type="password"
class="form-control pas" placeholder="請輸入原始密碼" autofocus required ngDefaultControl
/>
<ion-text text-left class="errorTip" color="danger" [hidden]="(!passForm.errors?.passWord.oldIsTrue) || passForm.get('passWord').pristine || passForm.value.passWord.length===0">*原始密碼錯誤</ion-text>
<input padding formControlName = "newPassWord" type="password"
class="form-control pas" placeholder="密碼長度為6-8位宴合,包含大小寫字母和數(shù)字" minLength="6" maxLength="8" required ngDefaultControl
/>
<ion-text text-left class="errorTip" color="danger" [hidden]="(!passForm.hasError('minlength','newPassWord')) || passForm.get('newPassWord').pristine || passForm.value.newPassWord.length===0">*密碼長度不小于6</ion-text>
<ion-text text-left class="errorTip" color="danger" [hidden]="(!passForm.hasError('maxlength','newPassWord')) || passForm.get('newPassWord').pristine || passForm.value.newPassWord.length===0">*密碼長度不大于8</ion-text>
<input padding formControlName = "newPassWord2" type="password"
class="form-control" placeholder="請再輸一次新密碼" minLength="6" maxLength="8" required ngDefaultControl
/>
<ion-text text-left class="errorTip" color="danger" [hidden]="(!passForm.errors?.passWord.newIsSame) || passForm.get('newPassWord2').pristine || (passForm.value.newPassWord.length===0 && passForm.value.newPassWord2.length===0)">*兩次密碼輸入不一致</ion-text>
<ion-button border-radius color="primary loginBtn" [disabled]="passForm.invalid" (click)="onSubmit()" expand="block">確定</ion-button>
</form>
<!-- <p>passForm:{{passForm.value| json}}</p> -->
<!-- <p>passForm.status:{{passForm.status }}</p>
<p>passForm.valid:{{passForm.invalid }}</p>
<p>passForm.errors.passWord?.newIsSame:{{passForm.errors?.passWord?.newIsSame}}</p>
<p>passForm.passWord:{{passForm.get('passWord').pristine}}</p> -->
import { FormGroup, FormControl, Validators, FormBuilder, AbstractControl } from '@angular/forms';
export class ModifyPasswordComponent implements OnInit {
passForm: FormGroup = this.fb.group({
passWord: ['',Validators.required],
newPassWord: ['',[Validators.required, Validators.maxLength(8), Validators.minLength(6)]],
newPassWord2:['',[Validators.required, Validators.maxLength(8), Validators.minLength(6)]]
},{validator: this.validPassword.bind(this)});
validPassword(group: FormGroup): any {
const passWord: FormControl = group.get('passWord') as FormControl;
const newPassWord: FormControl = group.get('newPassWord') as FormControl;
const newPassWord2: FormControl = group.get('newPassWord2') as FormControl;
const verify1:boolean = this.userSer.password === passWord.value;
const verify2:boolean = newPassWord.value === newPassWord2.value;
const valid:boolean = verify1 && verify2;
const obj = { passWord: {oldIsTrue: null,newIsSame:null}};
if(!verify1) {
obj.passWord.oldIsTrue = true;
}
if(!verify2) {
obj.passWord.newIsSame = true;
}
// console.log('密碼校驗結(jié)果是' + valid);
return valid ? null : obj;
}
}
改變表單的值setValue,patchvalue;
驗證器文檔:https://angular.cn/api/forms/AbstractControl
ng-valid: 驗證通過
ng-invalid: 驗證失敗
ng-valid-[key]: 由$setValidity添加的所有驗證通過的值
ng-invalid-[key]: 由$setValidity添加的所有驗證失敗的值
ng-pristine: 控件為初始狀態(tài) 用戶未操作過
ng-dirty: 控件輸入值已變更
ng-touched: 控件已失去焦點
ng-untouched: 控件未失去焦點
ng-pending: 任何為滿足$asyncValidators的情況
案例:以下圖注冊賬號的表單驗證為例
<form [formGroup]="regtForm" class="regForm">
//input框焕梅、驗證內(nèi)容需寫到form表單內(nèi)
</form>
創(chuàng)建表單變量
regtForm: FormGroup = this.fb.group({//用戶輸入的注冊信息
phoneNum: ['',Validators.required],
imgCaptcha:['',Validators.required],
captcha: ['',Validators.required],
passW:['',[Validators.required, Validators.maxLength(15), Validators.minLength(5)]]
},{validator: this.validReg.bind(this)});
constructor(private fb: FormBuilder) { }
>errors
一個對象,包含由失敗的驗證所生成的那些錯誤卦洽,如果沒出錯則為 null贞言。
validReg是自定義的表單驗證器:表單內(nèi)的驗證邏輯可以寫在這里
validReg(group: FormGroup):any {
const phoneNum: FormControl = group.get('phoneNum') as FormControl;
const val = phoneNum.value.toString();
// 當用戶選擇香港國家區(qū)號時 電話號碼長度為8位,選擇內(nèi)地國家區(qū)號時阀蒂,長度為11位
const verifyPhNum:boolean = (this.selIdx === 1 && val.length === 11) || (this.selIdx === 0 && val.length === 8);
const obj = {
phoneNum:true
};
return verifyPhNum ? null : obj;
}
如果驗證通過该窗,驗證器return null,如果驗證不通過弟蚀,則返回錯誤對象;
在html界面上獲取到驗證結(jié)果:
regtForm.errors?.phoneNum=true
說明電話號碼不符合規(guī)則酗失;
>value
獲取驗證碼按鈕的驗證規(guī)則:手機號碼和圖形驗證碼都填寫之后才能獲取短信驗證碼
<div class="wrap phoneCode" clearfix>
<ion-input formControlName="captcha" type="text" name="captcha"></ion-input>
<ion-button [disabled]="regtForm.errors?.phoneNum || regtForm.value.imgCaptcha.length==0>獲取驗證碼</ion-button>
</div>
regtForm.value.imgCaptcha
獲取綁定圖形驗證碼的值义钉;
可以通過regtForm.value.***
來獲取表單的值
<p>
{{regtForm.value|json}}
</p>
界面呈現(xiàn)結(jié)果:
>status
<p>
{{regtForm.status|json}}
</p>
控件的有效性狀態(tài)。有四個可能的值:
VALID: 該控件通過了所有有效性檢查规肴。
INVALID 該控件至少有一個有效性檢查失敗了捶闸。
PENDING:該控件正在進行有效性檢查,處于中間狀態(tài)拖刃。
DISABLED:該控件被禁用删壮,豁免了有效性檢查。
這些狀態(tài)值是互斥的兑牡,因此一個控件不可能同時處于有效狀態(tài)和無效狀態(tài)或無效狀態(tài)和禁用狀態(tài)央碟。
<pre>
regtForm.valid = {{regtForm.valid}}
regtForm.invalid = {{regtForm.invalid}}
regtForm.pending = {{regtForm.pending}}
regtForm.disabled = {{regtForm.disabled}}
regtForm.enabled = {{regtForm.enabled}}
</pre>
>pristine dirty
如果用戶尚未修改 UI 中的值,則該控件是 pristine(原始狀態(tài))的均函。那么
用戶未修改過UI 中的值:
用戶修改過UI 中的值:
>touched untouched
用戶尚未在控件上觸發(fā)過 blur 事件:
用戶尚在控件上觸發(fā)過 blur 事件:
>valueChanges statusChanges
valusChanges:一個多播 Observable(可觀察對象)淮韭,每當控件的值發(fā)生變化時,它就會發(fā)出一個事件 —— 無論是通過 UI 還是通過程序赶袄。
//控件值變化監(jiān)聽
this.regtForm.valueChanges.subscribe(res => {
console.log(res);
});
打印出該表單最新的值:
statusChanges:一個多播 Observable(可觀察對象)挟憔,每當控件的驗證 status 被重新計算時,就會發(fā)出一個事件墩朦。
//控件狀態(tài)變化監(jiān)聽
this.regtForm.statusChanges.subscribe(res => {
console.log(res);
});
打印的值可能是:valid 坯认、invalid 、pending 氓涣、disabled
>markAsTouched()
this.regtForm.controls['phoneNum'].markAsTouched({onlySelf:true});
>setValue() patchValue()
例如
regtForm= {
phoneNum: ‘’,
imgCaptcha:’’,
captcha: ‘’,
passW:’’
};
setValue()
設(shè)置該控件的值 相當于重新給form賦值
this.form .setValue({phoneNum: ‘1824567894 ‘});
之后再打印form結(jié)果是:
regtForm= {
phoneNum: ‘1824567894’
};
patchValue()
修補(patch)該控件的值 相當于修改form的某一個值
this.form .patchValue({phoneNum: ‘1824567894‘});
之后再打印form結(jié)果是:
regtForm= {
phoneNum: ‘1824567894’,
imgCaptcha:’’,
captcha: ‘’,
passW:’’
};
>get()
根據(jù)指定的控件名稱或路徑獲取子控件牛哺。
比如,要獲取子控件組 phoneNum中的 name 控件:this.form.get('person.name');
或 -this.form.get(['person', 'name']);
this.regtForm.get('phoneNum')
>getError()
getError(errorCode: string, path?: string[]): any
errorCode string 要獲取的數(shù)據(jù)的錯誤碼
path string[] 要檢查的控件的路徑劳吠。如果沒有提供該參數(shù)引润,則檢查該控件中的錯誤。
返回值:如果指定路徑下的控件具有指定的錯誤痒玩,則返回出錯數(shù)據(jù)淳附,否則為 null 或 undefined。
html:
{{regtForm.getError('minlength','passW')|json}}
ts:
console.log('getError:',this.regtForm.getError('minlength','passW'));
注意:'minlength'不能寫成'minLength'
當輸入值不符合最小長度時蠢古,打印結(jié)果:
達到最小長度要求時:
>hasError()
hasError(errorCode: string, path?: string[]): boolean
- errorCode string 要獲取的數(shù)據(jù)的錯誤碼
- path string[] 要檢查的控件的路徑奴曙。如果沒有提供該參數(shù),則檢查該控件中的錯誤草讶。
- 返回值:如果指定路徑下的控件有這個錯誤則返回 true洽糟,否則返回 false。
html:
{{regtForm.hasError('minlength','passW')|json}}
ts:
console.log('hasError:',this.regtForm.hasError('minlength','passW'));
不滿足最小長度時:hasError: true
滿足最小長度時:hasError: false