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 类来修改控件的外观,显示或隐藏消息。

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 状态)。

<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 实例。所以不能像响应式表单中那样把验证器传进去,而应该在模板中添加一个指令。