六。Angular — 响应式表单 + 表单验证

响应式表单提供了一种模型驱动的方式来处理表单输入,其中的值会随时间而变化。

Posted by Azr on January 30, 2019

响应式表单是围绕 Observable 的流构建的,表单的输入和值都是通过这些输入值组成的流来提供的,它可以同步访问。

响应式表单

表单分为响应式表单模板驱动表单

模板驱动表单就是基于模板语法,[(ngModel)]

响应式表单概念

  • 可以让开发人员完全掌控表单
  • 表单数据的初始化
  • 表单值的获取
  • 表单验证和提交

响应式表单优势

  • 提供了一种模型驱动的方式来处理表单输入– 数据驱动视图的思想
  • 同步的数据访问,保证数据和视图是一致的、可预测的
  • 增强了可测试性,让测试变的更简单
  • 内置表单验证器,可以自定义验证器

使用

基本使用
  1. 导入ReactiveFormsModule模块
  2. imports配置为当前模块的依赖项
  3. app.component.ts导入表单控件类
  4. 实例化FormControl,传递一个初始化值作为第一个参数
  5. app.component.html中通过formControl将用户中的数据与文本框绑定在一起

代码

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

// 1. 导入相应式表单的Module
import { ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  // 2. 配置为当前模块的依赖项
  imports: [
    BrowserModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';

// 3. 导入表单控件
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  // 4. 实例化FormControl,传递一个初始化值作为第一个参数。
  username = new FormControl('用户名test')
  password = new FormControl('')
}

app.component.html

<h1>表单</h1>
<div>
  <label>
    用户名:
    <!-- 5. 将用户中的数据与文本框绑定在一起 -->
    <input type="text" [formControl]="username">
  </label>
  <label>
    密码:
    <input type="password" [formControl]="password">
  </label>
  <hr>
  <p></p>
  <p></p>
</div>

value表示表单控件的值

用户名test

获取用户名

app.component.html

<button (click)='getUsername()'>获取用户的名字</button>

app.component.ts

....
export class AppComponent {
  username = new FormControl('用户名test')
  password = new FormControl('')
  .... 
  // 获取用户名
  getUsername() {
    console.log(this.username.value)
  }
}
更新用户名

app.component.html

<button (click)='setUserName()'>更新用户的名字</button>

app.component.ts

....
export class AppComponent {
  username = new FormControl('用户名test')
  password = new FormControl('')
  .... 
  // 更新用户名
  setUserName() {
    this.username.setValue('用户名变了')
  }
}

表单验证

表单验证分为内置表单验证器(Validators)和自定义验证器

内置表单验证器

  1. 导入内置表单验证器Validators

  2. FormControl的第二次参数中使用验证规则

    Validators.required ==> 必填项

    Validators.minLength(3) ==> 最小长度

    Validators.maxLength(6) ==> 最小长度

  3. 在页面中添加一个错误的描述信息,通过ngIf控制一下错误信息的展示隐藏

表单验证常用属性

  • 属性value 表示该表单控件的值
  • 属性errors 表示描述错误的对象
  • 属性dirty 表示是否编辑过表单
  • 方法hasError() 获取错误信息

代码

Validators.required

app.component.ts

import { Component, OnInit } from '@angular/core';

// 1. 导入内置表单验证器Validators
import { FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  // 2. 在FormControl的第二次参数中使用
  // Validators.required ==> 必填项
  username = new FormControl('', [ Validators.required ] )
  ngOnInit() {
    console.log(this.username)
  }
}

Console中查看可以看到errors,如下图显示。打印errors

当我们更改FormControl的第一个参数,使其不是空。那么errors就是变为null,如下图显示。

errors 就是表示当前的验证是否成功,成功就是null,失败就会显示出来错误信息。

errors 表示描述错误的对象

在页面app.component.html进行非空校验展示。当input是空的时候展示用户名为必填项,当不是空的时候,就隐藏掉。

<div>
  <label>用户名:<input type="text" [formControl]="username"></label>
  <p *ngIf="username.errors && username.errors.required">用户名为必填项</p>
</div

但是,正确的验证规则是,用户编辑过input 再去进行校验。

dirty 标记用户是否有在input进行编辑

<div>
  <label>用户名:<input type="text" [formControl]="username"></label>
  <p *ngIf="username.dirty && username.errors?.required">用户名为必填项</p>
</div>

也可以使用hasError()直接判断有没有required的错误信息

hasError():用来获取错误信息

<div>
  <label>用户名:<input type="text" [formControl]="username"></label>
  <p *ngIf="username.dirty && username.hasError('required')">用户名为必填项</p>
</div>

可以打印一下this.username.hasError('required')

import { Component, OnInit } from '@angular/core';

import { FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  // Validators.required ==> 必填项
  username = new FormControl('', [ Validators.required ] )
  ngOnInit() {
    console.log(this.username)
    // 如果有required错误,就返回true
    // 如果没有required错误,就返回false
    console.log(this.username.hasError('required'))
  }
}
Validators.minLength(n)

app.component.ts

import { Component } from '@angular/core';

import { FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  // Validators.minLength(3) ==> 最小长度
  username = new FormControl('', [ Validators.minLength(3) ] )
}

app.component.html

<div>
  <label>用户名:<input type="text" [formControl]="username"></label>
  <!-- 1. 用户名密码长度不能少于3位 -->
  <p *ngIf="username.dirty && username.hasError('minlength')">用户名密码长度不能少于3位</p>
</div>
Validators.maxLength(n)

app.component.ts

import { Component } from '@angular/core';

import { FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  // Validators.maxLength(6) ==> 最大长度
  username = new FormControl('', [ Validators.maxLength(6) ] )
}

app.component.html

<div>
  <label>用户名:<input type="text" [formControl]="username"></label>
  <!-- 2. 用户名密码长度不能大于6位 -->
  <p *ngIf="username.dirty && username.hasError('maxlength')">用户名密码长度不能大于6位</p>
</div>

自定义验证器说明

简单使用
  1. 自定义验证器是个函数customValidate
  2. 参数类型:AbstractControl
  3. 验证成功:返回 null
  4. 验证失败:返回一个描述错误的对象(自己定义)

app.component.ts

import { Component } from '@angular/core'

// 1. 导入AbstractControl
import { FormControl, AbstractControl } from '@angular/forms'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  username = new FormControl('',[this.customValidate])

  // 2. 参数类型:AbstractControl
  customValidate(control: AbstractControl) {
   
    if (/^[a-z]{3,6}$/.test(control.value)) {
      // 3. 成功 返回 null
      return null
    }
    // 4. 失败 返回 错误对象
    return { regerror: true }
  }
}

app.component.html

<div>
  <label>用户名:<input type="text" [formControl]="username"></label>
  <p *ngIf="username.dirty && username.hasError('regerror')">用户名为小写字母,长度为3到6位</p>
</div>
  1. 在 FormControl第二个参数提供一个自定义的函数会因为没有返回值而导致验证失败,先return null 就不会进行报错

    customValidate(control: AbstractControl) {
          return null
     }
    
  2. 先打印console.log(control),发现只有数据改变 ,就会更改自定义验证器

    customValidate(control: AbstractControl) {
    	console.log(control)
         return null
     }
    
使用FormGroup管理整个表单

FormGroup则可以为一组FormControl提供总包接口(wrapper interface)

FormGroupFormControl 都继承自同一个祖先AbstractControl

  1. app.component.ts中导入FormGroup
  2. 创建一个FormGroup的模型loginForm,用来控制整个表单,包含表单元素(FormControl)
  3. Angular提供了一个指令,可以使用现有的FormGroup,就是formGroup
  4. app.component.html还可以使用formControlName指令去指定一下用的是哪一个控制器
  5. 可以给一个ngSubmit来绑定一个提交表单事件

app.component.ts

import { Component } from '@angular/core'

// 1. 导入FormGroup
import { FormControl, FormGroup } from '@angular/forms'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  // 2 .创建一个FormGroup的模型loginForm
  //     包含FormControl
  loginForm = new FormGroup({
    username: new FormControl('123456'),
    password: new FormControl('qaz')
  })

  handleSubmit() {
    console.log('submit')
  }
}

app.component.html

<div>
  <!-- 3. formGroup -->
  <!-- 5. ngSubmit -->
  <form [formGroup]="loginForm" (ngSubmit)="handleSubmit()">
    <!-- 4. formControlName -->
      <label>用户名:<input type="text" formControlName="username"></label>
      <br>
      <label>用户名:<input type="text" formControlName="password"></label>
      <br>
      <button type="submit">提交表单</button>
  </form>
</div>

点击button按钮就会触发提交表单的事件,控制台就会打印出来submit

可以通过 this.loginForm.value来获取表单的值。

xxForm.valid
  1. 导入Validators添加表单验证
  2. 打印this.loginForm.valid
  3. xxForm.valid做为条件来做验证

app.component.ts

import { Component } from '@angular/core'

//  导入Validators
import { FormControl, FormGroup, Validators } from '@angular/forms'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
    
  loginForm = new FormGroup({
    username: new FormControl('123456'),
    // 添加表单验证
    password: new FormControl('', [Validators.required])
  })

  handleSubmit() {
    console.log('submit',  this.loginForm.value)
      
    // 打印this.loginForm.valid
    console.log('验证',  this.loginForm.valid)
      
    // 用xxForm.valid做为条件来做验证
    if (this.loginForm.valid) {
      console.log('表单验证成功, 发送请求提交表单')
    } else {
      console.log('表单验证失败, 提示用户表单验证失败')
    }
  }
}
使用FormGroup简化语法
  1. 导入FormBuilder
  2. constructor里面定义一下
  3. 简化loginForm

app.component.ts

import { Component } from '@angular/core'

// 导入FormBuilder
import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  // 去constructor里面定义一下
  constructor(private fb: FormBuilder) {}
  
  // loginForm = new FormGroup({
  //   username: new FormControl('123456'),
  //   password: new FormControl('', [Validators.required])
  // })
  
  // 简化loginForm
  loginForm = this.fb.group({
    username: [''],
    password: ['', Validators.required]
  })

  handleSubmit() {
    console.log('submit',  this.loginForm.value)
    console.log('验证',  this.loginForm.valid)
    if (this.loginForm.valid) {
      console.log('表单验证成功, 发送请求提交表单')
    } else {
      console.log('表单验证失败, 提示用户表单验证失败')
    }
  }
}

本文首次发布于 Azr的博客, 作者 @azrrrrr ,转载请保留原文链接.

原文链接: http://amor9.cn/2019/01/30/ng6/