Angular中表单分为两种,一种是是模板驱动表,一种是响应式表单。以下分别对模板驱动表单,响应式表单的使用,表单的验证做示例。
1. 模板驱动型表单
1.1模板表单的使用
要使用模板驱动行表单首先需要在根模块下面引入FormsModule,然后将FormsModule模块导入到根模块的ngModule中的imports数组中,这样才能够就能访问模板驱动表单的所有特性,包括 ngModel
模块的引用
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { ReactiveFormComponent } from './reactive-form/reactive-form.component';
import { HeroFormComponent } from './hero-form/hero-form.component';
import { RighsterFormComponent } from './righster-form/righster-form.component';
const routes: Routes = [
{
path: 'heroForm',
component: HeroFormComponent
},
{
path: 'loginForm',
component: LoginComponent
},
{
path: 'reactiveForm',
component: ReactiveFormComponent
},
{
path: 'registerForm',
component: RighsterFormComponent
},
{
path: '',
redirectTo: 'heroForm',
pathMatch: 'full'
}
];
@NgModule({
declarations: [
AppComponent,
LoginComponent,
ReactiveFormComponent,
HeroFormComponent,
RighsterFormComponent
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
RouterModule.forRoot(routes)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
1.2模板表单的详细介绍
新建一个login组件用来写一个注册的模板表单
其中模板为
<form action="/regist" method="post" #myForm="ngForm" (ngSubmit)="onSubmit(myForm.value)">
<div class="row"><label class="form-label">用户名:</label><input type="text" name="user" ngModel></div>
<div class="row"><label class="form-label">手机号:</label><input type="text" name="phone" ngModel></div>
<div class="row"><label class="form-label">邮编:</label><input type="number" name="youbian" ngModel></div>
<div ngModelGroup="passGroup">
<div class="row"><label class="form-label">密码:</label><input type="password" name="password" ngModel></div>
<div class="row"><label class="form-label">确认密码:</label><input type="password" name="repassword" ngModel></div>
</div>
<div class="row"><button type="submit">注册</button></div>
</form>
<div>{{ myForm.value | json }}</div>
ngForm:Angular 会在 <form>
标签上自动创建并附加一个 NgForm
指令。
NgForm
指令为 form
增补了一些额外特性。 它会控制那些带有 ngModel
指令和 name
属性的元素,监听他们的属性(包括其有效性)。 它还有自己的 valid
属性,这个属性只有在它包含的每个控件都有效时才是真。
myForm是一个模板变量
ngModel用于双向绑定,如上面的代码表单元素需要设置name属性,也可以写成另一种形式
<div class="row"><label class="form-label">用户名:</label><input type="text" name="user" [(ngModel)]="user"></div>
ngModelGroup可以用来分组
ngSubmit表单提交事件
myForm.value可以获取表单数据。
在表单中使用 ngModel
可以获得比仅使用双向数据绑定更多的控制权。它还会告诉你很多信息:用户碰过此控件吗?它的值变化了吗?数据变得无效了吗?
NgModel 指令不仅仅跟踪状态。它还使用特定的 Angular CSS 类来更新控件,以反映当前状态。 可以利用这些 CSS 类来修改控件的外观,显示或隐藏消息。
状态 | 为真时的 CSS 类 | 为假时的 CSS 类 |
---|---|---|
控件被访问过。 | ng-touched | ng-untouched |
控件的值变化了。 | ng-dirty | ng-pristine |
控件的值有效。 | ng-valid | ng-invalid |
2. 响应式表单
2.1响应式表单简介
响应式表单使用显式的、不可变的方式,管理表单在特定的时间点上的状态。对表单状态的每一次变更都会返回一个新的状态,这样可以在变化时维护模型的整体性。响应式表单是围绕 Observable 的流构建的,表单的输入和值都是通过这些输入值组成的流来提供的,同时,也赋予你对数据进行同步访问的能力。这种方式允许你的模板利用这些表单的“状态变更流”,而不必依赖它们。
响应式表单与模板驱动的表单有着显著的不同点。响应式表单通过对数据模型的同步访问提供了更多的可预测性,使用 Observable 的操作符提供了不可变性,并且通过 Observable 流提供了变化追踪功能。
2.2 响应式表单具体使用
要使用响应式表单,首先需要在@angular/forms包中导入ReactiveFormsModule,并把它放到ngModule的imports数组中去。
FormControl:
当使用响应式表单时,FormControl
是最基本的构造块。要注册单个的表单控件,请在组件中导入 FormControl
类,并创建一个 FormControl
的新实例,把它保存在类的某个属性中。
FormGroup
正如 FormControl
的实例能让你控制单个输入框所对应的控件,FormGroup
可以跟踪一组 FormControl
实例(比如一个表单)的表单状态。当创建 FormGroup
时,其中的每个控件都会根据其名字进行跟踪。
FormBuilder
当需要与多个表单打交道时,手动创建多个表单控件实例会非常繁琐。FormBuilder
服务提供了一些便捷方法来生成表单控件。FormBuilder
在幕后也使用同样的方式来创建和返回这些实例,用起来更简单。 ,用 FormBuilder
来代替手工创建这些 FormControl
和 FormGroup
。
FormArray
FormArray
是 FormGroup
之外的另一个选择,用于管理任意数量的匿名控件。像 FormGroup
实例一样,你也可以往 FormArray
中动态插入和移除控件,并且 FormArray
实例的值和验证状态也是根据它的子控件计算得来的。 不过,你不需要为每个控件定义一个名字作为 key,因此,如果你事先不知道子控件的数量,这就是一个很好的选择。
表单部分模型更新 :
当修改包含多个控件的 FormGroup
的值时,你可能只希望更新模型中的一部分,而不是完全替换掉。
修补(Patch)模型值
对单个控件,你会使用 setValue()
方法来该控件设置新值。但当应用到 FormGroup
并打算整体设置该控件的值时,setValue()
方法会受到这个 FormGroup
结构的很多约束。patchValue()
方法就宽松多了,它只会替换表单模型中修改过的那些属性,因为你只想提供部分修改。setValue()
中严格的检查可以帮你捕获复杂表单嵌套时可能出现的错误,而 patchValue()
将会默默地走向失败。
eg:
this.profileForm.patchValue({
firstName: 'Nancy',
address: {
street: '123 Drew Street'
}
});
先举一个不使用FormBuilder服务创建控件的方法
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, FormArray } from '@angular/forms';
@Component({
selector: 'app-reactive-form',
templateUrl: './reactive-form.component.html',
styleUrls: ['./reactive-form.component.css']
})
export class ReactiveFormComponent implements OnInit {
formModel: FormGroup = new FormGroup({
dateRange: new FormGroup({
from: new FormControl(),
to: new FormControl()
}),
emails: new FormArray([
new FormControl('aaa@a.com'),
new FormControl('bbb@b.com'),
]
)
});
username: FormControl = new FormControl('aa');
constructor() { }
ngOnInit() {
}
addEmail(): void {
const emails = this.formModel.get('emails') as FormArray;
emails.push(new FormControl());
}
onSubmit(): void {
console.log(this.formModel);
}
}
模板
<form [formGroup]="formModel" (submit)="onSubmit()">
<div formGroupName="dateRange">
起始日期: <input type="date" formControlName="from">
截止日期: <input type="date" formControlName="to">
</div>
<div>
<ul formArrayName="emails">
<li *ngFor="let email of this.formModel.get('emails').controls;let i=index;">
<input type="text" [formControlName]="i">
</li>
</ul>
<button type="button" (click)="addEmail()">增加email</button>
</div>
<button type="submit">保存</button>
</form>
再举一个使用FormBulider服务创建表单控件的例子,生成一个注册组件
import { Component, OnInit } from '@angular/core';
import {FormGroup, FormBuilder, FormControl, Validators } from '@angular/forms';
import { mobileValidator, equalValidator } from '../validator/validators';
@Component({
selector: 'app-righster-form',
templateUrl: './righster-form.component.html',
styleUrls: ['./righster-form.component.css']
})
export class RighsterFormComponent implements OnInit {
rigisterForm: FormGroup;
isCanSubmit: boolean;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.createForm();
}
createForm() {
this.rigisterForm = this.fb.group({
username: ['', [Validators.required, Validators.minLength(6)]],
mobile: ['', mobileValidator],
passwordGroup: this.fb.group({
password: ['', [Validators.minLength(6)]],
pconfirm: ['']
}, {validator: equalValidator})
});
}
// 提交
onSubmit() {
const isValid: boolean = this.rigisterForm.get('username').valid;
// console.log(isValid);
// console.log( this.rigisterForm.get('username'));
if (this.rigisterForm.valid) {
console.log(this.rigisterForm.value);
}
}
}
其中关于验证的可以先忽略;
模板代码:
<h2>用户注册</h2>
<form [formGroup]="rigisterForm" (submit)="onSubmit()">
<div class="row">
<label>用户名:</label><input type="text" formControlName="username">
</div>
<div [hidden]="rigisterForm.get('username').valid || rigisterForm.get('username').untouched">
<div [hidden]="!rigisterForm.hasError('required', 'username')">
用户名是必填项
</div>
<div [hidden]="!rigisterForm.hasError('minlength', 'username')">
用户名最小长度是6
</div>
</div>
<div class="row">
<label>手机号:</label><input type="text" formControlName="mobile">
</div>
<div [hidden]="rigisterForm.get('mobile').valid || rigisterForm.get('mobile').pristine">
<div [hidden]="!rigisterForm.hasError('mobile', 'mobile')">
请输入正确的手机号码
</div>
</div>
<div formGroupName="passwordGroup">
<div class="row">
<label>密码:</label><input type="password" formControlName="password">
</div>
<div [hidden]="!rigisterForm.hasError('minLength', ['passwordGroup', 'password'])">
密码长度最少6位
</div>
<div class="row">
<label>确认密码:</label><input type="password" formControlName="pconfirm">
</div>
<div [hidden]="!rigisterForm.hasError('equal', 'passwordGroup')">
两次密码输入不一致
</div>
</div>
<div class="row">
<button type="submit">提交</button>
</div>
</form>
<p>{{ rigisterForm.status }}</p>
可以使用rigisterForm.get(formControlName).value获取每个控件的值,
rigisterForm.value可以获取整个表单的数据
rigisterForm.status可以获取表单验证是否通过,是表单所有的验证规则。
3.表单验证
3. 1模板驱动验证
为了往模板驱动表单中添加验证机制,你要添加一些验证属性,就像原生的 HTML 表单验证器。 Angular 会用指令来匹配这些具有验证功能的指令。
每当表单控件中的值发生变化时,Angular 就会进行验证,并生成一个验证错误的列表(对应着 INVALID 状态)或者 null(对应着 VALID 状态)。
eg:
<input id="name" name="name" class="form-control"
required minlength="4" appForbiddenName="bob"
[(ngModel)]="hero.name" #name="ngModel" >
<div *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<div *ngIf="name.errors.required">
Name is required.
</div>
<div *ngIf="name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div *ngIf="name.errors.forbiddenName">
Name cannot be Bob.
</div>
</div>
3.2响应式表单验证
在响应式表单中,真正的源码都在组件类中。不应该通过模板上的属性来添加验证器,而应该在组件类中直接把验证器函数添加到表单控件模型上(FormControl
)。然后,一旦控件发生了变化,Angular 就会调用这些函数。
验证器函数
有两种验证器函数:同步验证器和异步验证器。
- 同步验证器函数接受一个控件实例,然后返回一组验证错误或
null
。你可以在实例化一个FormControl
时把它作为构造函数的第二个参数传进去。 - 异步验证器函数接受一个控件实例,并返回一个承诺(Promise)或可观察对象(Observable),它们稍后会发出一组验证错误或者
null
。你可以在实例化一个FormControl
时把它作为构造函数的第三个参数传进去。
上面2.2中最后一个例子使用的就是响应式表单验证。
自定义验证器,2.2最后一个例子就有两个自定义验证器,mobileValidator和equalValidator分别验证手机号和密码,自定义验证器代码如下:
import {FormGroup, FormControl } from '@angular/forms';
export function mobileValidator(control: FormControl): any {
const myreg = /^1[0-9]{10}$/;
const valid = myreg.test(control.value);
console.log('mobile的校验值为' + valid);
return valid ? null : { mobile: true };
}
export function equalValidator(group: FormGroup): any {
const pass = group.get('password') as FormControl;
const pconfirm = group.get('pconfirm') as FormControl;
const valid: boolean = (pass.value === pconfirm.value);
console.log('密码校验结果' + valid);
return valid ? null : { equal: true };
}
自定义表单添加到响应式表单中很简单,直接把这个函数放到FormControl就行了。
添加到模板驱动表单
在模板驱动表单中,你不用直接访问 FormControl
实例。所以不能像响应式表单中那样把验证器传进去,而应该在模板中添加一个指令。