看了兩天官網文檔表單和表單驗證後,才發現以前本身僅僅經過特定的 Angular CSS 類去控制去反饋用戶的操做,如ng-touched等等是多麼的入門,雖說這已經夠用了,可是以前寫的都是在視圖層上寫邏輯,各類判斷,致使後期維護有點困難。趁有時間記錄一下這兩天看了資料的一些心得。javascript
二者均可以經過表單綁定獲取整個表單的值和是否合法eg: myForm.value, myForm.valid等等html
使用方便java
適用於簡單的場景react
經過 [(ngModel)] 實現數據雙向綁定json
最小化組件類的代碼segmentfault
不易於單元測試數組
導入FormModule函數
表單綁定經過 #myForm="ngForm"單元測試
比較靈活測試
適用於複雜的場景
簡化了HTML模板的代碼,把驗證邏輯抽離到組件類中
方便的跟蹤表單控件值的變化
易於單元測試
導入ReactiveFormsModule
表單綁定經過 [formGroup]="myForm"
import { Component, AfterViewChecked, ViewChild } from '@angular/core'; import { NgForm } from '@angular/forms'; import { Hero } from '../shared/hero'; @Component({ selector: 'hero-form-template2', templateUrl: './hero-form-template2.component.html' }) export class HeroFormTemplate2Component implements AfterViewChecked { powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); submitted = false; onSubmit() { this.submitted = true; } // Reset the form with a new hero AND restore 'pristine' class state // by toggling 'active' flag which causes the form // to be removed/re-added in a tick via NgIf // TODO: Workaround until NgForm has a reset method (#6822) active = true; addHero() { this.hero = new Hero(42, '', ''); this.active = false; setTimeout(() => this.active = true, 0); } /*組件手動記錄表單對象*/ heroForm: NgForm; /*heroFrom變量是Angular從模板衍生出來的控制模型的引用。 咱們利用@ViewChild來告訴Angular注入這個模型到組件類的currentForm*/ @ViewChild('heroForm') currentForm: NgForm; /*監聽視圖中表格變化,第一次渲染表格對象有多少個值就跑多少次*/ ngAfterViewChecked() { this.formChanged(); } formChanged() { /*檢測是否發生變化*/ if (this.currentForm === this.heroForm) { return; } this.heroForm = this.currentForm; if (this.heroForm) { /*heroForm獲得的是FormModule對象,該對象繼承於AbstractControl抽象類,裏面包含touched等*/ /*valueChanges返回一個Observal,用於獲取視圖返回的可觀察對象*/ this.heroForm.valueChanges .subscribe(data => this.onValueChanged(data)); } } onValueChanged(data?: any) { if (!this.heroForm) { return; } const form = this.heroForm.form; for (const field in this.formErrors) { // clear previous error message (if any) this.formErrors[field] = ''; /*heroForm.form.get('name')獲取對應表單裏的鍵值*/ const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += messages[key] + ' '; } } } } formErrors = { 'name': '', 'power': '' }; validationMessages = { 'name': { 'required': 'Name is required.', 'minlength': 'Name must be at least 4 characters long.', 'maxlength': 'Name cannot be more than 24 characters long.', 'forbiddenName': 'Someone named "Bob" cannot be a hero.' }, 'power': { 'required': 'Power is required.' } }; }
import { Component, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { Hero } from '../shared/hero'; import { forbiddenNameValidator } from '../shared/forbidden-name.directive'; @Component({ ... }) export class HeroFormReactiveComponent implements OnInit { ... /**/ heroForm: FormGroup; constructor(private fb: FormBuilder) { } ngOnInit(): void { /*初始化表單對象*/ this.buildForm(); } buildForm(): void { /*FormBuilder.group是一個用來建立FormGroup的工廠方法*/ this.heroForm = this.fb.group({ /*這裏的值接受兩個參數,第一個是值,第二個是檢驗器,若是有多個檢驗器必須用數組表示*/ 'name': [this.hero.name, [ Validators.required, Validators.minLength(4), Validators.maxLength(24), forbiddenNameValidator(/bob/i) ] ], 'alterEgo': [this.hero.alterEgo], 'power': [this.hero.power, Validators.required] }); this.heroForm.valueChanges .subscribe(data => this.onValueChanged(data)); this.onValueChanged(); // (re)set validation messages now } /*與驅動表單上面代碼相似*/ ... }
表組經過ngModelGroup去綁定
<form #templateForm="ngForm" novalidate (ngSubmit)="onSubmit(templateForm)"> <span>Name</span> <label class="user_name"> <input #uName="ngModel" type="text" name="user_name" [(ngModel)]="user.name" required> </label> <div ngModelGroup="account"> <span>QQ</span> <label class="user_name"> <input type="text" name="user_qq" [(ngModel)]="user.account.qq" required> </label> <span>Password</span> <label class="user_name"> <input type="password" name="user_password" [(ngModel)]="user.account.password" required> </label> </div> <button style="width: 200px; height: 36px; border: 1px solid #ccc; margin-top: 20px;" [disabled]="templateForm.invalid">提交</button> </form>
表組經過
formGroupName="address"
-------------------------------------------------------------------
html:
這裏注意要經過表單.get的方法獲取FormModel的東西,reactiveForm.get('age').touched 或者reactive.controls.age?.touched
<form [formGroup]="reactiveForm" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" class="form-control" formControlName="name"> </div> <div class="form-group" [ngClass]="{'has-error': (reactiveForm.get('age').touched || reactiveForm.get('age').dirty) && !reactiveForm.get('age').valid }"> <label class="col-md-2 control-label" for="ageId">年齡</label> <div class="col-md-8"> <input class="form-control" id="ageId" type="number" placeholder="請輸入年齡" formControlName="age" /> <span class="help-block" *ngIf="(reactiveForm.get('age').touched || reactiveForm.get('age').dirty) && reactiveForm.get('age').errors"> <span *ngIf="reactiveForm.get('age').errors.range"> 輸入年齡不合法 </span> </span> </div> </div> <p>{{reactiveForm.value | json}}</p> </form>
ts:
... @Component({ ... }) export class ReactiveFormComponent implements OnInit { user: any = { name: '', age: 0 }; reactiveForm: FormGroup; constructor(public fb: FormBuilder) { } ngOnInit() { this.buildForm(); } buildForm(): void { this.reactiveForm = this.fb.group({ 'name': [this.user.name, Validators.required], 'age': [this.user.age, this.ageValidator] //自定義驗證方法使用 }); // this.reactiveForm.valueChanges // .subscribe(data => { console.log(data) }); } /*有參數的驗證器工廠函數寫法,這裏FormControl和下面的AbstractControl都是指向抽象類的屬性和方法都是爲了獲取表單屬性*/ private ageValidatorParams(min: number, max: number) { return (c: FormControl): { [key: string]: any } | null => { let age = c.value; if (age && (isNaN(age) || age < min || age > max)) { return { 'range': true, min: min, max: max }; } return null; } } /*必須返回一個對象,該對象爲錯誤信息的值*/ private ageValidator(c: AbstractControl): { [key: string]: any } | null { let age = c.value; if (age && (isNaN(age) || age < 20 || age > 120)) { return { 'range': true, min: 20, max: 120 }; } return null; } }
參考文獻:
二、模板驅動式表單
三、響應式表單