前言
表單在整個系統中的作用相當重要,這裡主要扯下響應表單的實現方式。
首先需要操作表單的模組引入這兩個模組;import {
FormsModule, ReactiveFormsModule
} from '
@angular/forms'
;
表單控制元件響應的幾種狀態
模板驅動表單依賴FormsModule
,資料驅動的表單依賴FormsModule,ReactiveFormsModule
一般做表單校驗及操作推薦用資料驅動的方式,好維護和理解。。
模板驅動
模板驅動:主要是依賴[(ngModel)]
和#scope_var
以及原生表單控制元件屬性(require
,minlenght
,maxlength
等)來操作表單的那的值亦或者校驗
- 一個最簡單的例子
<
!--#UserName 是區域性變數,若是有ngmodel,拿到的就是一個響應物件,若是非ngmodel繫結的,則是dom元素程式碼-->
<
!--testform這個區域性變數儲存了表單的所有相關資訊-->
<
!--ngSubmit是用來觸發表單提交的-->
<
!--ngModel相應變數的值-->
<
!--$event是原生dom物件-->
<
form #testform="ngForm" (ngSubmit)="Submit(testform.value,testform.valid)">
<
label for="username">
Name<
/label>
<
input type="text" id="username" #UserName="username" class="form-control" required minlength="4" maxlength="24" name="username" [(ngModel)]="username" [ngModelChange]="validate($event)">
<
div *ngIf="UserName.valid || (UserName.pristine &
&
!testform.submitted)">
您輸入的值有誤,請重新輸入<
/div>
<
button type="submit" >
提交<
/button>
<
/form>
複製程式碼
有兩種方式處理來對上面的表單做校驗;
- 在
Submit()
函式內,在點選提交的時候對整個表單一一去判斷,傳統方式基本這樣 - 每個控制元件輸入的時候對應去觸發對應的事件做校驗,比如
[ngModelChange]
來處理雙向繫結的值校驗
資料驅動(Reactive Form)
響應式表表單:原理是一開始就構建整個表單,表單的值通過特殊指令formControlName
一一關聯(類似ngModel
);
相關名詞:
FormGroup
: 用來追蹤表單控制元件有效狀態及值 =》 可以理解為獲取且可以操作整個表單的資料FormBuilder
:表單資料構建工具[構建初始表單],簡化構建程式碼(包括了new FormGroup()
,new FormControl()
,new FormArray()
),FormGroup()
內建多種校驗方式formControlName
: 同步與FormGroup
構建表單內相同欄位的值!
專案中的案例
- html
<
div [@flyIn]="true">
<
div class="beautify-form" *ngIf="!showLoading">
<
div class="page-header">
歡迎登入 <
/div>
<
form [formGroup]="form" (ngSubmit)="onSubmit(form)">
<
div class="form-group" [ngClass]="{
'has-danger': form.controls.UserName.invalid &
&
form.controls.UserName.value ,'has-success': form.controls.UserName.valid &
&
form.controls.UserName.value
}">
<
div class="input-group input-group-lg">
<
span class="input-group-addon fpd fpd-ordinarylogin1">
<
/span>
<
input type="text" class="form-control" formControlName="UserName" placeholder="手機號碼 \ 郵箱 ">
<
/div>
<
div class="form-control-feedback" *ngIf="(form.controls.UserName.dirty || form.controls.UserName.pristine) &
&
form.controls.UserName.invalid &
&
form.controls.UserName.value">
賬號不符合規範<
/div>
<
div class="form-control-feedback" *ngIf="(form.controls.UserName.dirty || form.controls.UserName.pristine) &
&
form.controls.UserName.valid &
&
form.controls.UserName.value">
賬號符合規範<
/div>
<
/div>
<
div class="form-group" [ngClass]="{
'has-danger': form.controls.PassWord.invalid &
&
form.controls.PassWord.value ,'has-success': form.controls.PassWord.valid &
&
form.controls.PassWord.value
}">
<
div class="input-group input-group-lg">
<
span class="input-group-addon fpd fpd-mima">
<
/span>
<
input type="PassWord" class="form-control" formControlName="PassWord" placeholder="請輸入密碼">
<
/div>
<
div class="form-control-feedback" *ngIf="(form.controls.PassWord.dirty || form.controls.PassWord.pristine) &
&
form.controls.PassWord.invalid &
&
form.controls.PassWord.value ">
密碼不符合規範,請重新輸入<
/div>
<
div class="form-control-feedback" *ngIf="(form.controls.PassWord.dirty || form.controls.PassWord.pristine) &
&
form.controls.PassWord.valid &
&
form.controls.PassWord.value ">
密碼符合規範<
/div>
<
/div>
<
div class="form-group ">
<
div class="flex">
<
div class="beautify-wrap flex-wrap">
<
input type="checkbox" class="beautify-checkbox" name="rememberme" id="rememberAccount" formControlName="rememberAccount">
<
label for="rememberAccount">
<
/label>
記住賬號 <
/div>
<
!--<
a [routerLink]="['/account/reset-pw']">
忘記密碼<
/a>
-->
<
/div>
<
/div>
<
div class="message-tips" *ngIf="messageTips">
<
i class="fpd fpd-error">
<
/i>
{{messageTips
}
} <
/div>
<
div class="form-group ">
<
button class="btn btn-lg btn-outline-success btn-block" type="submit" [disabled]="form.invalid">
登入<
/button>
<
/div>
<
div class="form-group">
<
span class="noaccount-notify">
沒有賬號?點選<
/span>
<
a [routerLink]="['/account/collect']" class="collect-user">
使用者登記<
/a>
<
/div>
<
/form>
<
/div>
<
div class="loading" *ngIf="showLoading">
<
app-mit-loading [option]="'load4'">
<
/app-mit-loading>
<
/div>
<
/div>
複製程式碼
- component.ts
import {
Component, OnInit, OnDestroy
} from '@angular/core';
import {
FormGroup, Validators, FormBuilder
} from '@angular/forms';
// 引入表單的一些特性import {
Router
} from '@angular/router';
import {
AccountService
} from '../../services/account.service';
import {
environment
} from '../../../../../environments/environment';
import {
flyIn
} from '../../../../animation/flyIn';
import {
Observable
} from 'rxjs/Observable';
@Component({
selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.scss'], animations: [flyIn]
})export class LoginComponent implements OnInit, OnDestroy {
public form: FormGroup;
// 表單物件 public showLoading = false;
public messageTips: string;
public login_subscribe: any;
// Validators的寫法注意事項 // v2.x版本這樣的寫法是可行的,v4有調整,不然不會生效 // 'UserName':'', [ Validators.compose([Validators.minLength(6)] // v4+ , 第一位的''代表這個元素初始化構建為空值,類似未輸入狀態 // 'UserName': ['', Validators.compose([Validators.minLength(6)] // Validators可選引數 // 1. required :必須驗證的,返回布林值 // 2. minLength : 最小長度 // 3. maxLenght: 最大長度 // 4. nullValidator : 空值判斷 // 5. coompose :多重判斷組合,下面有寫法 // 6. pattern是支援正則模式,正則謹記轉義轉義轉義 constructor(private fb: FormBuilder, private router: Router, private account: AccountService) {
this.form = fb.group({
'UserName': ['', Validators.compose([Validators.minLength(6) || Validators.pattern('(0|86|17951)?(-)?1[3,4,5,7,8,9]\\d{9
}') || Validators.pattern('[\\.a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+')])], 'PassWord': ['', Validators.compose([Validators.required, Validators.pattern('\\w{8,16
}')])], 'rememberAccount': ['']
});
} ngOnInit() {
} // 登入事件 onSubmit(e) {
this.showLoading = true;
this.login_subscribe = this.account.login(e.value).subscribe((res) =>
{
console.log('省略。。。。。。')
}, (err) =>
{
this.showLoading = false;
});
} ngOnDestroy() {
if (this.login_subscribe) {
this.login_subscribe.unsubscribe();
}
}
}複製程式碼
效果圖
巢狀表單
有些時候我們介面資料層次不可能只有一層,出現兩層三層都有可能;
這時候需要我們構建一個巢狀表單。。。
- html
v2-的寫法:表單的取值可以用controls
直接點出來
<
div class="custom-card">
<
div class="custom-card-body">
<
form [formGroup]="form" (ngSubmit)="onSubmit(form.value)">
<
div class="row" formGroupName="RuleContent">
<
div class="col-sm-12 col-md-12 col-lg-8 offset-lg-2">
<
div class="form-group row" [ngClass]="{
'has-danger': form.controls.RuleContent.controls.FenceName.invalid &
&
form.controls.RuleContent.controls.FenceName.value ,'has-success': form.controls.RuleContent.controls.FenceName.valid &
&
form.controls.RuleContent.controls.FenceName.value
}">
<
label tooltip="" class="col-sm-10 col-md-3 form-control-label col-lg-3 star">
速度柵欄名稱<
/label>
<
div class="col-sm-8 col-md-6 col-lg-6">
<
input type="text" class="form-control" formControlName="FenceName" placeholder="柵欄名稱">
<
/div>
<
div class="col-2 col-sm-4 col-lg-3 flex-align-center">
不超過十個字 <
/div>
<
/div>
<
div class="form-group row" [ngClass]="{
'has-danger': form.controls.RuleContent.controls.MaxSpeed.invalid &
&
form.controls.RuleContent.controls.MaxSpeed.value ,'has-success': form.controls.RuleContent.controls.MaxSpeed.valid &
&
form.controls.RuleContent.controls.MaxSpeed.value
}">
<
label tooltip="" class="col-sm-10 col-md-3 form-control-label col-lg-3 star">
速度閾值<
/label>
<
div class="col-sm-8 col-md-6 col-lg-6">
<
input type="number" class="form-control" min="1" formControlName="MaxSpeed" placeholder="整數">
<
/div>
<
div class="col-2 col-sm-4 col-lg-3 flex-align-center">
km/h <
/div>
<
/div>
<
div class="form-group row">
<
div class="col-12 col-sm-10 col-md-6 offset-sm-2 offset-md-4 offset-lg-3">
<
button type="submit" class="btn btn-primary" [disabled]="form.invalid">
儲存<
/button>
<
button type="button" class="btn btn-secondary" (click)="back()">
取消<
/button>
<
/div>
<
/div>
<
/div>
<
/div>
<
/form>
<
/div>
<
/div>
複製程式碼
v4+的寫法 :巢狀表單的取值必須用.get()
來獲取,不然會報錯誤,具體原因是api改動了,看下官方文件就知道,改動了挺多(不僅僅這塊)
<
div class="custom-card">
<
div class="custom-card-body">
<
form [formGroup]="form" (ngSubmit)="onSubmit(form.value)">
<
div class="row" formGroupName="RuleContent">
<
div class="col-sm-12 col-md-12 col-lg-8 offset-lg-2">
<
div class="form-group row" [ngClass]="{
'has-danger': form.get('RuleContent.FenceName').invalid &
&
form.get('RuleContent.FenceName').value ,'has-success': form.get('RuleContent.FenceName').valid &
&
form.get('RuleContent.FenceName').value
}">
<
label tooltip="" class="col-sm-10 col-md-3 form-control-label col-lg-3 star">
速度柵欄名稱<
/label>
<
div class="col-sm-8 col-md-6 col-lg-6">
<
input type="text" class="form-control" formControlName="FenceName" placeholder="柵欄名稱">
<
/div>
<
div class="col-2 col-sm-4 col-lg-3 flex-align-center">
不超過十個字 <
/div>
<
/div>
<
div class="form-group row" [ngClass]="{
'has-danger': form.get('RuleContent.MaxSpeed').invalid &
&
form.get('RuleContent.MaxSpeed').value ,'has-success': form.get('RuleContent.MaxSpeed').valid &
&
form.get('RuleContent.MaxSpeed').value
}">
<
label tooltip="" class="col-sm-10 col-md-3 form-control-label col-lg-3 star">
速度閾值<
/label>
<
div class="col-sm-8 col-md-6 col-lg-6">
<
input type="number" class="form-control" min="1" formControlName="MaxSpeed" placeholder="整數">
<
/div>
<
div class="col-2 col-sm-4 col-lg-3 flex-align-center">
km/h <
/div>
<
/div>
<
div class="form-group row">
<
div class="col-12 col-sm-10 col-md-6 offset-sm-2 offset-md-4 offset-lg-3">
<
button type="submit" class="btn btn-primary" [disabled]="form.invalid">
儲存<
/button>
<
button type="button" class="btn btn-secondary" (click)="back()">
取消<
/button>
<
/div>
<
/div>
<
/div>
<
/div>
<
/form>
<
/div>
<
/div>
複製程式碼
- components.ts
import {
Component, OnInit
} from '@angular/core';
import {
Router, ActivatedRoute
} from '@angular/router';
import {
FormGroup, FormControl, Validators, FormBuilder
} from '@angular/forms';
// 引入表單的一些特性// 動畫import {
fadeIn
} from '../../../../../animation/fadeIn';
// 服務import {
SpeedFenceService
} from '../speed-fence.service';
import {
EventsService
} from '../../../../../services/events-service.service';
@Component({
selector: 'app-modify', templateUrl: './modify.component.html', styleUrls: ['./modify.component.scss'], animations: [fadeIn]
})export class ModifyComponent implements OnInit {
public form: FormGroup;
public getId: any;
public id: number;
constructor( private speedFenceService: SpeedFenceService, private eventsService: EventsService, private router: Router, private activatedRoute: ActivatedRoute, private fb: FormBuilder ) {
this.form = fb.group({
'ID': 0, 'RuleContent': this.fb.group({
'MaxSpeed': [0, Validators.compose([Validators.required, Validators.pattern('(([4-9][0-9])|(1[0-1][0-9])|(120))')])], 'FenceName': ['', Validators.compose([Validators.required, Validators.minLength(2), Validators.maxLength(10)])],
})
});
} ngOnInit() {
this.checkAction();
// console.log(this.form);
} // 獲取ID checkAction() {
this.activatedRoute.params.subscribe((params: {
id: string
}) =>
{
console.log(params);
if (params.id) {
console.log(this.id);
this.id = parseInt(params.id, 10);
this.form.controls['ID'].setValue(this.id);
this.GetSpeedFenceSettingByFenceId({
FenceId: parseInt(params.id, 10)
});
}
});
} GetSpeedFenceSettingByFenceId(data) {
this.speedFenceService.GetSpeedFenceSettingByFenceId(data).subscribe( res =>
{
if (res.State) {
this.form.controls['RuleContent'].setValue({
'MaxSpeed': res.Data.RuleContent.MaxSpeed || '', 'FenceName': res.Data.RuleContent.FenceName || '',
});
}
}, err =>
{
} );
} onSubmit(form) {
console.log('此處省略。。。。。');
} // 取消 back() {
if (this.id) {
this.router.navigate(['../../'], {
relativeTo: this.activatedRoute
});
} else {
this.router.navigate(['../'], {
relativeTo: this.activatedRoute
});
}
}
}複製程式碼
總結
多看手冊多動手,才是真理。。
有不足之處或者錯誤之處請留言指出,會及時跟進修正。。謝謝