Overview angular 入坑记录的笔记第三篇,介绍 angular 中表单控件的相关概念,了解如何在 angular 中创建一个表单,以及如何针对表单控件进行数据校验。
对应官方文档地址:
配套代码地址:angular-practice/src/forms-overview 
 
Contents 
Angular 从入坑到弃坑 - Angular 使用入门  
Angular 从入坑到挖坑 - 组件食用指南  
Angular 从入坑到挖坑 - 表单控件概览  
 
Knowledge Graph 
Step by Step 表单简介 用来处理用户的输入,通过从视图中捕获用户的输入事件、验证用户输入的是否满足条件,从而创建出表单模型修改组件中的数据模型,达到获取用户输入数据的功能
 
模板驱动表单 
响应式表单 
 
 
建立表单 
由组件隐式的创建表单控件实例 
在组件类中进行显示的创建控件实例 
 
表单验证 
指令 
函数 
 
 
在表单数据发生变更时,模板驱动表单通过修改 ngModel 绑定的数据模型来完成数据更新,而响应式表单在表单数据发生变更时,FormControl 实例会返回一个新的数据模型,而不是直接修改原来的数据模型
模板驱动表单 通过使用表单的专属指令(例如 ngModel 进行双向数据绑定)将数据值和一些对于用户的行为约束(某个字段必须填啊、某个字段长度超过了长度限制啊)绑定到组件的模板中,从而完成与用户的交互
模板驱动表单的双向数据绑定 在根模块中引入 FormsModule,并添加到根模块的 imports 数组中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import  { BrowserModule } from  '@angular/platform-browser' ;import  { NgModule } from  '@angular/core' ;import  { FormsModule } from  '@angular/forms' ;import  { AppRoutingModule } from  './app-routing.module' ;import  { AppComponent } from  './app.component' ;import  { TemplateDrivenFormsComponent } from  './template-driven-forms/template-driven-forms.component' ;@NgModule ({  declarations: [     AppComponent,     ReactiveFormsComponent,     DynamicFormsComponent,     TemplateDrivenFormsComponent   ],   imports: [     BrowserModule,     AppRoutingModule,     FormsModule    ],   providers: [],   bootstrap: [AppComponent] }) export  class  AppModule { }
 
新建一个类文件,用来承载组件与模板之间进行双向数据绑定的数据信息
 
1 2 3 4 5 6 7 8 9 10 11 12 export  class  Hero {     constructor (public  name: string , public  age: number , public  gender: string , public  location: string  ) {   } } 
 
在组件的模板中创建承载数据的表单信息,并使用 ngModel 完成组件与模板之间的数据双向绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <form >   <div  class ="form-group" >      <label  for ="name" > 姓名:</label >      <input  type ="text"  name ="name"  id ="name"  [(ngModel )]="hero.name"  class ="form-control"  autocomplete ="off"  required  minlength ="4" >    </div >    <div  class ="form-group" >      <label  for ="age" > 年龄:</label >      <input  type ="number"  name ="age"  id ="age"  [(ngModel )]="hero.age"  class ="form-control"  required >    </div >    <div  class ="form-group" >      <label  for ="gender" > 性别:</label >      <div  class ="form-check"  *ngFor ="let gender of genders" >        <input  class ="form-check-input"  type ="radio"  name ="gender"  id ="{{gender.id}}"  value ="{{gender.value}}"           [(ngModel )]="hero.gender" >       <label  class ="form-check-label"  for ="{{gender.id}}" >          {{gender.text}}       </label >      </div >    </div >    <div  class ="form-group" >      <label  for ="location" > 住址:</label >      <select  name ="location"  id ="location"  [(ngModel )]="hero.location"  class ="form-control"  required >        <option  value ="{{location}}"  *ngFor ="let location of locations" > {{location}}</option >      </select >    </div >    <button  type ="submit"  (click )="submit()"  class ="btn btn-primary" > Submit</button >  </form > <p >   表单的数据信息:{{hero | json}} </p > 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import  { Component, OnInit } from  '@angular/core' ;import  { Hero } from  './../classes/hero' ;@Component ({  selector: 'app-template-driven-forms' ,   templateUrl: './template-driven-forms.component.html' ,   styleUrls: ['./template-driven-forms.component.scss' ] }) export  class  TemplateDrivenFormsComponent implements  OnInit {  constructor ( ) { }      public  genders = [{     id: 'male' , text: '男' , value: true    }, {     id: 'female' , text: '女' , value: false    }];      public  locations: Array <string > = ['beijing' , 'shanghai' , 'hangzhou' , 'wuhan' ];   hero = new  Hero('' , 18 , 'true' , 'beijing' );   ngOnInit(): void  {   }   submit() {   } } 
 
在使用 ngModel 进行模板绑定时,angular 在 form 标签上自动附加了一个 NgForm 指令,因为 NgForm 指令会控制表单中带有 ngModel 指令和 name 属性的元素,而 name 属性则是 angular 用来注册控件的 key,所以在表单中使用 ngModel 进行双向数据绑定时,必须要添加 name 属性
跟踪表单控件的状态 在表单中使用 ngModel 之后,NgModel 指令通过更新控件的 css 类,达到反映控件状态的目的
状态 
发生时的 css 类 
没发生的 css 类 
 
 
控件被访问 
ng-touched 
ng-untouched 
 
控件的值发生变化 
ng-dirty 
ng-pristine 
 
控件的值是否有效 
ng-valid 
ng-invalid 
 
 
通过这些控件的 css 类样式,就可以通过添加自定义的 css 样式在用户输入内容不满足条件时进行提示
1 2 3 4 5 6 7 .ng-valid [required] , .ng-valid .required   {  border-left : 5px  solid #42A948 ;  } .ng-invalid :not(form)   {  border-left : 5px  solid #a94442 ;  } 
 
数据的有效性验证 某些时候需要对于用户输入的信息做有效性验证,此时可以在控件上添加上原生的 HTML 表单验证器 来设定验证条件,当表单控件的数据发生变化时,angular 会通过指令的方式对数据进行验证,从而生成错误信息列表
在进行用户输入数据有效性验证时,在控件上通过添加一个模板引用变量来暴露出 ngModel,从而在模板中获取到指定控件的状态信息,之后就可以通过获取错误信息列表来进行反馈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <div  class ="form-group" >     <label  for ="name" > 姓名:</label >           <input  type ="text"  name ="name"  id ="name"  [(ngModel )]="hero.name"  class ="form-control"  autocomplete ="off"  required         minlength ="4"  #name ="ngModel" >          <div  *ngIf ="name.invalid && (name.dirty || name.touched)"  class ="alert alert-danger" >        <div  *ngIf ="name.errors.required" >          姓名不能为空       </div >        <div  *ngIf ="name.errors.minlength" >          姓名信息不能少于 4 个字符长度       </div >      </div >    </div >  
 
在数据验证失败的情况下,对于系统来说,表单是不允许提交的,因此可以将提交事件绑定到表单的 ngSubmit 事件属性上,通过模板引用变量的形式,在提交按钮处进行数据有效性判断,当无效时,禁用表单的提交按钮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <form  (ngSubmit )="submit()"  #heroForm ="ngForm" >   <div  class ="form-group" >      <label  for ="name" > 姓名:</label >           <input  type ="text"  name ="name"  id ="name"  [(ngModel )]="hero.name"  class ="form-control"  autocomplete ="off"  required         minlength ="4"  #name ="ngModel" >          <div  *ngIf ="name.invalid && (name.dirty || name.touched)"  class ="alert alert-danger" >        <div  *ngIf ="name.errors.required" >          姓名不能为空       </div >        <div  *ngIf ="name.errors.minlength" >          姓名信息不能少于 4 个字符长度       </div >      </div >    </div >    <div  class ="form-group" >      <label  for ="age" > 年龄:</label >      <input  type ="number"  name ="age"  id ="age"  [(ngModel )]="hero.age"  class ="form-control"  required >    </div >    <div  class ="form-group" >      <label  for ="gender" > 性别:</label >      <div  class ="form-check"  *ngFor ="let gender of genders" >        <input  class ="form-check-input"  type ="radio"  name ="gender"  id ="{{gender.id}}"  value ="{{gender.value}}"           [(ngModel )]="hero.gender" >       <label  class ="form-check-label"  for ="{{gender.id}}" >          {{gender.text}}       </label >      </div >    </div >    <div  class ="form-group" >      <label  for ="location" > 住址:</label >      <select  name ="location"  id ="location"  [(ngModel )]="hero.location"  class ="form-control"  required >        <option  value ="{{location}}"  *ngFor ="let location of locations" > {{location}}</option >      </select >    </div >    <button  type ="submit"  [disabled ]="!heroForm.form.valid"  class ="btn btn-primary" > Submit</button >  </form > 
 
响应式表单 快速上手 响应式表单依赖于  ReactiveFormsModule 模块,因此在使用前需要在根模块中引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import  { BrowserModule } from  '@angular/platform-browser' ;import  { NgModule } from  '@angular/core' ;import  { ReactiveFormsModule } from  '@angular/forms' ;import  { AppRoutingModule } from  './app-routing.module' ;import  { AppComponent } from  './app.component' ;import  { ReactiveFormsComponent } from  './reactive-forms/reactive-forms.component' ;@NgModule ({  declarations: [     AppComponent,     ReactiveFormsComponent,     DynamicFormsComponent,     TemplateDrivenFormsComponent   ],   imports: [     BrowserModule,     AppRoutingModule,     ReactiveFormsModule    ],   providers: [],   bootstrap: [AppComponent] }) export  class  AppModule { }
 
在使用响应式表单时,一个 FormControl 类的实例对应于一个表单控件,在使用时,通过将控件的实例赋值给属性,后续则可以通过监听这个自定义的属性来跟踪表单控件的值和状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import  { Component, OnInit } from  '@angular/core' ;import  { FormControl } from  '@angular/forms' ;@Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     public  name = new  FormControl('' );   constructor ( ) { }   ngOnInit(): void  {   } } 
 
当在组件中创建好控件实例后,通过给视图模板上的表单控件添加 formControl 属性绑定,从而将控件实例与模板中的表单控件关联起来
1 2 3 4 5 6 7 8 9 10 11 <form >   <div  class ="form-group" >      <label  for ="name" > 姓名:</label >      <input  type ="text"  id ="name"  [formControl ]='name'  class ="form-control"  autocomplete ="off" >    </div >  </form > <div >   name 控件的数据值: {{ name | json }} </div > 
 
通过使用 FormControl 控件的 value 属性,可以获得当前表单控件的一份数据值拷贝,通过 setValue 方法则可以更新表单的控件值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import  { Component, OnInit } from  '@angular/core' ;import  { FormControl } from  '@angular/forms' ;@Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     public  name = new  FormControl('12345' );   constructor ( ) { }   ngOnInit(): void  {   }   getName() {     alert(this .name.value);   }   setName() {     this .name.setValue(1111111 );   } } 
 
通过 FomGroup 组合多个控件 一个表单不可能只有一个控件,通过在组件中构造 FormGroup 实例来完成对于多个表单控件的统一管理
在使用 FormGroup 时,同样在组件中定义一个属性用来承载控件组实例,然后将控件组中的每一个控件作为属性值添加到实例中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import  { Component, OnInit } from  '@angular/core' ;import  { FormControl, FormGroup } from  '@angular/forms' ;@Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     public  profileForm = new  FormGroup({     name: new  FormControl('啦啦啦' ),     age: new  FormControl(12 )   });   constructor ( ) { }   ngOnInit(): void  {   } } 
 
在视图模板中,将承接 FormGroup 实例的属性通过 formGroup 指令绑定到 form 元素,然后将控件组的每一个属性通过 formControlName 绑定到具体对应的表单控件上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <form  [formGroup ]='profileForm' >   <div  class ="form-group" >      <label  for ="name" > 姓名:</label >      <input  type ="text"  id ="name"  formControlName ='name'  class ="form-control"  autocomplete ="off"  required  minlength ="4" >    </div >    <div  class ="form-group" >      <label  for ="age" > 年龄:</label >      <input  type ="number"  id ="age"  formControlName ='age'  class ="form-control"  autocomplete ="off"  required  step ="1"         max ="100"  min ="1" >   </div >  </form > <div >   FormGroup 表单组控件的值: {{ profileForm.value | json }} </div > 
 
当构建复杂表单时,可以在 FormGroup 中通过嵌套 FormGroup 使表单的结构更合理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import  { Component, OnInit } from  '@angular/core' ;import  { FormControl, FormGroup } from  '@angular/forms' ;@Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     public  profileForm = new  FormGroup({     name: new  FormControl('啦啦啦' ),     age: new  FormControl(12 ),     address: new  FormGroup({       province: new  FormControl('北京市' ),       city: new  FormControl('北京' ),       district: new  FormControl('朝阳区' ),       street: new  FormControl('三里屯街道' )     })   });   constructor ( ) { }   ngOnInit(): void  {   }   submit() {     alert(JSON .stringify(this .profileForm.value));   } } 
 
在视图模板中,通过使用 formGroupName 属性将 FormGroup 控件组中的 FormGroup 实例绑定到控件上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <form  [formGroup ]='profileForm'  (ngSubmit )='submit()' >   <div  class ="form-group" >      <label  for ="name" > 姓名:</label >      <input  type ="text"  id ="name"  formControlName ='name'  class ="form-control"  autocomplete ="off"  required  minlength ="4" >    </div >    <div  class ="form-group" >      <label  for ="age" > 年龄:</label >      <input  type ="number"  id ="age"  formControlName ='age'  class ="form-control"  autocomplete ="off"  required  step ="1"         max ="100"  min ="1" >   </div >    <div  formGroupName ='address' >      <div  class ="form-group" >        <label  for ="province" > 省:</label >        <input  type ="text"  id ="province"  formControlName ='province'  class ="form-control"  autocomplete ="off"  required >      </div >      <div  class ="form-group" >        <label  for ="city" > 市:</label >        <input  type ="text"  id ="city"  formControlName ='city'  class ="form-control"  autocomplete ="off"  required >      </div >      <div  class ="form-group" >        <label  for ="district" > 区:</label >        <input  type ="text"  id ="district"  formControlName ='district'  class ="form-control"  autocomplete ="off"  required >      </div >      <div  class ="form-group" >        <label  for ="street" > 街道:</label >        <input  type ="text"  id ="street"  formControlName ='street'  class ="form-control"  autocomplete ="off"  required >      </div >    </div >    <button  type ="submit"  class ="btn btn-primary"  [disabled ]="!profileForm.valid" > 数据提交</button >  </form > <div >   FormGroup 表单组控件的值: {{ profileForm.value | json }} </div > 
 
对于使用了 FormGroup 的表单来说,当使用 setValue 进行数据更新时,必须保证新的数据结构与原来的结构相同,否则就会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import  { Component, OnInit } from  '@angular/core' ;import  { FormControl, FormGroup } from  '@angular/forms' ;@Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     public  profileForm = new  FormGroup({     name: new  FormControl('啦啦啦' ),     age: new  FormControl(12 ),     address: new  FormGroup({       province: new  FormControl('北京市' ),       city: new  FormControl('北京' ),       district: new  FormControl('朝阳区' ),       street: new  FormControl('三里屯街道' )     })   });   constructor ( ) { }   ngOnInit(): void  {   }   submit() {     alert(JSON .stringify(this .profileForm.value));   }   updateProfile() {     this .profileForm.setValue({       name: '423'      });   } } 
 
某些情况下,我们只是想要更新控件组中的某个控件的数据值,这时需要使用 patchValue 的方式进行更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import  { Component, OnInit } from  '@angular/core' ;import  { FormControl, FormGroup } from  '@angular/forms' ;@Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     public  profileForm = new  FormGroup({     name: new  FormControl('啦啦啦' ),     age: new  FormControl(12 ),     address: new  FormGroup({       province: new  FormControl('北京市' ),       city: new  FormControl('北京' ),       district: new  FormControl('朝阳区' ),       street: new  FormControl('三里屯街道' )     })   });   constructor ( ) { }   ngOnInit(): void  {   }   submit() {     alert(JSON .stringify(this .profileForm.value));   }   updateProfile() {     this .profileForm.patchValue({       name: '12345'      });   } } 
 
当控件过多时,通过 FormGroup or FormControl 手动的构建表单控件的方式会很麻烦,因此这里可以通过依赖注入 FormBuilder 类的方式来简化的完成表单的构建
FormBuilder 服务有三个方法:control、group 和 array,用于在组件类中分别生成 FormControl、FormGroup 和 FormArray
使用 FormBuilder 构建的控件,每个控件名对应的值都是一个数组,第一个值为控件的默认值,第二项和第三项则是针对这个值设定的同步、异步验证方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import  { Component, OnInit } from  '@angular/core' ;import  { FormBuilder } from  '@angular/forms' ;@Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     constructor (private  formBuilder: FormBuilder ) { }   public  profileForm = this .formBuilder.group({     name: ['啦啦啦' ],     age: [12 ],     address: this .formBuilder.group({       province: ['北京市' ],       city: ['北京' ],       district: ['朝阳区' ],       street: ['三里屯街道' ]     })   });   ngOnInit(): void  {   } } 
 
数据的有效性验证 同模板驱动表单的数据有效性验证相同,在响应式表单中同样可以使用原生的表单验证器,在设定规则时,需要将模板中控件名对应的数据值的第二个参数改为验证的规则
在响应式表单中,数据源来源于组件类,因此应该在组件类中直接把验证器函数添加到对应的 FormControl 的构造函数上。然后,一旦控件数据发生了变化,angular 就会调用这些函数
这里创建针对指定控件的 getter 方法,从而在模板中通过此方法来获取到指定控件的状态信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import  { Component, OnInit } from  '@angular/core' ;import  { FormBuilder } from  '@angular/forms' ;import  { Validators } from  '@angular/forms' ;@Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     constructor (private  formBuilder: FormBuilder ) { }   public  profileForm = this .formBuilder.group({     name: ['' , [       Validators.required,       Validators.minLength(4 )     ]],     age: [12 ],     address: this .formBuilder.group({       province: ['北京市' ],       city: ['北京' ],       district: ['朝阳区' ],       street: ['三里屯街道' ]     })   });      get  name() {     return  this .profileForm.get('name' );   }   ngOnInit(): void  {   } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <form  [formGroup ]='profileForm'  (ngSubmit )='submit()' >   <div  class ="form-group" >      <label  for ="name" > 姓名:</label >      <input  type ="text"  id ="name"  formControlName ='name'  class ="form-control"  autocomplete ="off"  required  minlength ="4" >           <div  *ngIf ="name.invalid && (name.dirty || name.touched)"  class ="alert alert-danger" >        <div  *ngIf ="name.errors.required" >          姓名不能为空       </div >        <div  *ngIf ="name.errors.minlength" >          姓名信息不能少于 4 个字符长度       </div >      </div >    </div >    <div  class ="form-group" >      <label  for ="age" > 年龄:</label >      <input  type ="number"  id ="age"  formControlName ='age'  class ="form-control"  autocomplete ="off"  required  step ="1"         max ="100"  min ="1" >   </div >    <div  formGroupName ='address' >      <div  class ="form-group" >        <label  for ="province" > 省:</label >        <input  type ="text"  id ="province"  formControlName ='province'  class ="form-control"  autocomplete ="off"  required >      </div >      <div  class ="form-group" >        <label  for ="city" > 市:</label >        <input  type ="text"  id ="city"  formControlName ='city'  class ="form-control"  autocomplete ="off"  required >      </div >      <div  class ="form-group" >        <label  for ="district" > 区:</label >        <input  type ="text"  id ="district"  formControlName ='district'  class ="form-control"  autocomplete ="off"  required >      </div >      <div  class ="form-group" >        <label  for ="street" > 街道:</label >        <input  type ="text"  id ="street"  formControlName ='street'  class ="form-control"  autocomplete ="off"  required >      </div >    </div >    <button  type ="button"  class ="btn btn-primary"  (click )="updateProfile()" > 更新信息</button >      <button  type ="submit"  class ="btn btn-primary"  [disabled ]="!profileForm.valid" > 数据提交</button >  </form > <div >   FormGroup 表单组控件的值: {{ profileForm.value | json }} </div > 
 
表单的自定义数据验证 自定义验证器 在很多的情况下,原生的验证规则无法满足我们的需要,此时需要创建自定义的验证器来实现
对于响应式表单,我们可以定义一个方法,对控件的数据进行校验,之后将方法作为参数添加到控件定义处即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import  { Component, OnInit } from  '@angular/core' ;import  { FormBuilder } from  '@angular/forms' ;import  { Validators } from  '@angular/forms' ;function  validatorName (name: FormControl )  {  return  name.value === 'lala'  ? { nameinvalid: true  } : null ; } @Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     constructor (private  formBuilder: FormBuilder ) { }   public  profileForm = this .formBuilder.group({     name: ['' , [       Validators.required,       Validators.minLength(4 ),       validatorName      ]],     age: [12 ],     address: this .formBuilder.group({       province: ['北京市' ],       city: ['北京' ],       district: ['朝阳区' ],       street: ['三里屯街道' ]     })   });      get  name() {     return  this .profileForm.get('name' );   }   ngOnInit(): void  {   } } 
 
在验证方法中,当数据有效时,返回 null,当数据无效时,则会返回一个对象信息,这里的 nameinvalid 就是我们在模板中获取到的错误信息的 key 值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <div  class ="form-group" >     <label  for ="name" > 姓名:</label >      <input  type ="text"  id ="name"  formControlName ='name'  class ="form-control"  autocomplete ="off"  required  minlength ="4" >           <div  *ngIf ="name.invalid && (name.dirty || name.touched)"  class ="alert alert-danger" >        <div  *ngIf ="name.errors.required" >          姓名不能为空       </div >        <div  *ngIf ="name.errors.minlength" >          姓名信息不能少于 4 个字符长度       </div >        <div  *ngIf ="name.errors.nameinvalid" >          姓名无效       </div >      </div >  </div > 
 
在模板驱动表单中,因为不是直接使用的 FormControl 实例,因此这里应该在模板上添加一个自定义的指令来完成对于控件数据的校验
使用 angular cli 创建一个用来进行表单验证的指令
1 ng g directive direactives/hero-validate 
 
在创建完成指令之后,我们需要将这个指令将该验证器添加到已经存在的验证器集合中,同时为了使这个指令可以与 angular 表单集成在一起,我们需要继承 Validator 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import  { Directive, Input } from  '@angular/core' ;import  { AbstractControl, Validator, ValidationErrors, NG_VALIDATORS } from  '@angular/forms' ;@Directive ({  selector: '[appHeroValidate]' ,      providers: [{ provide: NG_VALIDATORS, useExisting: HeroValidateDirective, multi: true  }] }) export  class  HeroValidateDirective implements  Validator {  constructor ( ) { }      validate(control: AbstractControl): ValidationErrors | null  {     return  control.value === 'lala'  ? { 'nameInvalid' : true  } : null ;   } } 
 
当实现了继承的 validate 方法后,就可以在模板的控件上添加该指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <div  class ="form-group" >     <label  for ="name" > 姓名:</label >           <input  type ="text"  name ="name"  id ="name"  [(ngModel )]="hero.name"  class ="form-control"  autocomplete ="off"  required         minlength ="4"  #name ="ngModel"  appHeroValidate >          <div  *ngIf ="name.invalid && (name.dirty || name.touched)"  class ="alert alert-danger" >        <div  *ngIf ="name.errors.required" >          姓名不能为空       </div >        <div  *ngIf ="name.errors.minlength" >          姓名信息不能少于 4 个字符长度       </div >        <div  *ngIf ="name.errors.nameInvalid" >          姓名无效       </div >      </div >  </div > 
 
跨字段的交叉验证 有时候需要针对表单中的多个控件数据进行交叉验证,此时就需要针对整个 FormGroup 进行验证。因此这里的验证方法需要在定义控件组时作为 FormGroup 的参数传入
与单个字段的验证方式相似,通过实现 ValidatorFn 接口,当表单数据有效时,它返回一个 null,否则返回 ValidationErrors 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import  { Component, OnInit } from  '@angular/core' ;import  { FormControl, FormGroup, ValidatorFn, ValidationErrors } from  '@angular/forms' ;import  { FormBuilder } from  '@angular/forms' ;import  { Validators } from  '@angular/forms' ;const  nameAgeCrossValidator: ValidatorFn = (controlGroup: FormGroup): ValidationErrors | null  =>  {        const  name = controlGroup.get('name' );   const  age = controlGroup.get('age' );   return  name && age && name.value === 'lala'  && age.value === 12  ? { 'nameAgeInvalid' : true  } : null ; }; @Component ({  selector: 'app-reactive-forms' ,   templateUrl: './reactive-forms.component.html' ,   styleUrls: ['./reactive-forms.component.scss' ] }) export  class  ReactiveFormsComponent implements  OnInit {     constructor (private  formBuilder: FormBuilder ) { }   public  profileForm = this .formBuilder.group({     name: ['' , [       Validators.required,       Validators.minLength(4 ),       validatorName     ]],     age: [12 ],     address: this .formBuilder.group({       province: ['北京市' ],       city: ['北京' ],       district: ['朝阳区' ],       street: ['三里屯街道' ]     })   }, { validators: [nameAgeCrossValidator] });       ngOnInit(): void  {   } } 
 
在针对多个字段进行交叉验证时,在模板页面中,则需要通过获取整个表单的错误对象信息来获取到交叉验证的错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <div  class ="form-group" >     <label  for ="name" > 姓名:</label >      <input  type ="text"  id ="name"  formControlName ='name'  class ="form-control"  autocomplete ="off"  required  minlength ="4" >           <div  *ngIf ="name.invalid && (name.dirty || name.touched)"  class ="alert alert-danger" >        <div  *ngIf ="name.errors.required" >          姓名不能为空       </div >        <div  *ngIf ="name.errors.minlength" >          姓名信息不能少于 4 个字符长度       </div >        <div  *ngIf ="name.errors.nameinvalid" >          姓名无效       </div >      </div >    </div >    <div  class ="form-group" >      <label  for ="age" > 年龄:</label >      <input  type ="number"  id ="age"  formControlName ='age'  class ="form-control"  autocomplete ="off"  required  step ="1"         max ="100"  min ="1" >     <div  *ngIf ="profileForm.errors?.nameAgeInvalid && (profileForm.touched || profileForm.dirty)"         class ="alert alert-danger" >       lala 不能是 12 岁     </div >  </div > 
 
对于模板驱动表单,同样是采用自定义指令的方式进行跨字段的交叉验证,与单个控件的验证不同,此时需要将指令添加到 form 标签上,然后使用模板引用变量来获取错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import  { Directive } from  '@angular/core' ;import  { Validator, AbstractControl, ValidationErrors, ValidatorFn, FormGroup, NG_VALIDATORS } from  '@angular/forms' ;const  nameAgeCrossValidator: ValidatorFn = (controlGroup: FormGroup): ValidationErrors | null  =>  {        const  name = controlGroup.get('name' );   const  age = controlGroup.get('age' );   return  name && age && name.value === 'lala'  && age.value === 12  ? { 'nameAgeInvalid' : true  } : null ; }; @Directive ({  selector: '[appCrossFieldValidate]' ,   providers: [{ provide: NG_VALIDATORS, useExisting: CrossFieldValidateDirective, multi: true  }] }) export  class  CrossFieldValidateDirective implements  Validator {  constructor ( ) { }   validate(control: AbstractControl): ValidationErrors | null  {     return  nameAgeCrossValidator(control);   } }