angular 响应式表单FormGroup和模型驱动表单[(ngModel)] —angular学习笔记4

响应式表单和模型驱动表单相同与不同之处

  • 相同:两者都从视图中捕获用户输入事件、验证用户输入、创建表单模型、修改数据模型,并提供跟踪这些更改的途径。
  • 不同:主要是在如何处理和管理表单和表单数据方面有所不同。具体方面:
不同响应式模板驱动
建立(表单模式)显式,在组件类中创建
例如:this.myForm = new FormGroup({})
隐式,由组件创建。
例如[(ngModel)]=“favoriteColor”
数据模式结构化非结构化
可预测性同步异步
表单验证函数指令
可变性不可变可变
可伸缩性访问底层 API在 API 之上的抽象
总结响应式直接访问底层api性能更好,适用于复杂表单模板驱动的底层原理相同,但是访问的时上层API适用于简单表单
  • 响应式表单方法,需要显式创建 FormControl 对象,并使用 formControl 或 formControlName 指令来绑定原生控件。

响应式表单创建案例

html<表单>
写法一:
<div [formGroup]="myForm"> // 通过myForm变量与ts关联
  // formControlName: 是输入指令,同formGroup 指令配合使用。
  <input type="text" formControlName="firstName"/>
</div>
写法一的html也可以写成如下方式:
//等同于
<div> 
  <input type="text" [formControl]="myForm.controls.firstName"/>
</div>

------

写法二:
<label>
// [formControl]:是输入指令,接受 FormControl 的实例。
  <input type="text" [formControl]="name">
</label>

 js部分
 1.在app.moudle.ts中引入并注册
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 imports: [
    FormsModule,
    ReactiveFormsModule,
    ]
 2.在需要使用的ts页面再次引入并使用
 import { FormGroup,FormControl, Validators} from '@angular/forms';
 // 引入FormGroup是监控整个表单
 // 引入FormControl是监控表单控件
 // 引入Validators是为了校验表单数据
 export class AppComponent implements OnInit {

1.写法一:[FormGroup](对应上方html写法)
this.myForm = new FormGroup({  // 创建整个表单监控
   firstName: new FormControl(''), // 创建单个组件监控
  });
  
2.写法二:[formControl]
name = new FormControl('',Validators.required);
console.log(name.value);   // ''
console.log(name.status);  // 'INVALID'
this.name.setValue('Nancy'); // 修改控件的值

}

  • 模板驱动方法,FormControl 对象会被 NgModel 指令隐式创建

模板驱动表单创建案例

<input type="text" [(ngModel)]="favoriteColor">
<label> 
    <span>电子邮件地址</span> 
    <input type="text" name="email" 
    placeholder="请输入您的 email 地址" 
    [ngModel]="user.email" 
    minlength="8"
    required 
    pattern="([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]"+
    "@([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,4}">
</label>

表单底层原理(底层构造块):

  • FormControl实例:用于追踪/监控单个表单控件的值和验证状态。(例如:监控一个input框等)
  • FormGroup 用于追踪一个表单控件组的值和状态。(例如:监控一个form里面的所有input)
  • FormArray 用于追踪表单控件数组的值和状态。(例如input框绑定的变量是一个数组)
  • ControlValueAccessor 用于在 Angular 的 FormControl 实例和原生 DOM 元素之间创建一个桥梁。
FormGroup:跟踪一组 FormControl 实例的值和有效性状态
FormGroup 把每个子 FormControl 的值聚合进一个对象,它的 key 是每个控件的名字。
它通过归集其子控件的状态值来计算出自己的状态。如果组中的任何一个控件是无效的,那么整个组就是无效的。
FormGroup 实例拥有和 FormControl 实例相同的属性(比如 value、untouched)和方法(比如 setValue()。

FormArray:跟踪一个控件数组的值和有效性状态
FormArray 聚合了数组中每个表单控件的值。它会根据其所有子控件的状态总结出自己的状态。
如果 FromArray 中的任何一个控件是无效的,那么整个数组也会变成无效的。

响应式表单fromGroup与FormBuilder对比

1.对表单内容进行分组fromGroup
2.form 标签所发出的 submit 事件是原生 DOM 事件,通过点击类型为 submit 的按钮可以触发本事件。
这还让用户可以用回车键来提交填完的表单。

<form [formGrop] = 'myForm'  (ngSubmit)="onSubmit()">
  <label>
    名字:
    <input type="text" formControlName="name">
  </label>
/* 嵌套表单 formGroupName */
    <div class="form-group" formGroupName="address">
        <label>地址:</label>
        <div>
          <label>省:</label>
          <input type="text"
            formControlName="province">
          <p>{{errorMessage('province')}}</p>
        </div>
        <div>
          <label>市:</label>
          <input type="text"
            formControlName="city">
          <p>{{errorMessage('city')}}</p>
        </div>
        <div>
          <label>区:</label>
          <input type="text"
            formControlName="district">
          <p>{{errorMessage('district')}}</p>
        </div>
      </div>
      <button class="btn btn-primary" type="submit"  [disabled]="!myForm.valid">
            <i class="fa fa-search m-r-xs"></i>
            <span>提交</span>
      </button>
  </form>
  
/***  
注意:上面这个代码片段中的按钮还附加了一个 disabled 绑定,用于在 myForm 无效时禁用该按钮。
目前还没有执行任何表单验证逻辑,因此该按钮始终是可用的。稍后的表单验证一节会讲解简单的表单验证。 
***/

1.1.再在xx.ts部分用FormGroup定义表单:


 1.在app.moudle.ts中引入并注册
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 imports: [
    FormsModule,
    ReactiveFormsModule,
    ]
1.1.再在xx.ts部分用FormGroup定义表单:

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

export class ProfileEditorComponent {
  myForm = new FormGroup({
    name: new FormControl(''),
    age: new FormControl(''),
    sex: new FormControl(''),
    address: new FormGroup({ // address 此时不是 fromControl 而是 formGroup
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl('')
    })
  });
}

1.1或者再在xx.ts部分用FormBuilder定义表单:


1.1或者再在xx.ts部分用FormBuilder定义表单:

使用FormBuilder定义表单,来便捷的生成表单控件,也就是定义表单的简便方法

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

constructor(private fb: FormBuilder) { 

// FormBuilder 服务有三个方法:control()、group() 和 array()。
// 这些方法用于在组件类中分别生成 FormControl、FormGroup 和 FormArray。

   this.myForm = this.fb.group({
      name: ['', nameValidator()],
      age: ['', ageValidator()],
      sex: ['', sexValidator()],
      address: this.fb.group({
        province: ['', requiredValidator('请输入省')],
        city: ['', requiredValidator('请输入市')],
        district: ['', requiredValidator('请输入区')]
      })
    }); 
}

2.保存/更新/监听表单数据


2.保存表单数据
// 当点击提交button时(type="submit"),会触发<form>表单上(ngSubmit)绑定的事件

  onSubmit() {
    // TODO: Use EventEmitter with form value
    console.warn(this.myForm.value);
  }

3.更新表单中的数据:
3.1方法一:
使用 setValue() 方法来为单个控件设置新值。 setValue() 方法会严格遵循表单组的结构,并整体性替换控件的值。
updateProfile() {
  this.myForm.setValue({
    name: 'Nancy',
    demion:'123', // demion在表单中不存在,setValue会整个失败,但是pathValue会更新
    address: {
      street: '123 Drew Street'
    }
  });
}
3.2方法二:
使用 patchValue() 方法可以用对象中所定义的任何属性为表单模型进行替换。
updateProfile() {
  this.myForm.patchValue({
    name: 'Nancy',
    address: {
      street: '123 Drew Street'
    }
  });
}

// patchValue() 和 setValue() 这两个方法是等价的。 setValue() 方法做了三件事:
1.更新控件当前值
2.判断是否注册 onChange 事件,若有则循环调用已注册的 changeFn 函数。
3.重新计算控件的值和验证状态

//  区别:setValue() 方法相比 patchValue() 会更严格,会执行多个判断
// 判断是否为所有控件都设置更新值
// 判断控件是否存在

3.监听表单中的数据:用valueChanges时时订阅事件
    this.name.valueChanges.subscribe((newValue) => {
      console.log(newValue);
    });

表单的验证方法

内置Validators 验证

import { FormBuilder, Validators } from '@angular/forms'; 
1.1 验证表单
把表单控件中的变量设置为必填:Validators.required
  this.myForm = this.fb.group({
  // 同步验证器
   name: ['', [
      Validators.required, //必填
      Validators.minLength(4), //最少4个字
      forbiddenNameValidator(/bob/i) // 禁止输入的名字中有bob
    ]],
   age: ['', ageValidator()],
  })
  
get nameVar() { 
 // 一定要有getter属性,html模板中nameVar.errors.required中的nameVar就是通过getter获取的
    return this.heroForm.get('name'); 
}
// 或者在html上
<input type="text" formControlName="name" required  minlength="4" >
<div *ngIf="name.invalid && (name.dirty || name.touched)">
      <div *ngIf="nameVar.errors.required">
       名字必填
      </div>
      <div *ngIf="nameVar.errors.minlength">
        名字的长度不能小于4
      </div>
      <div *ngIf="nameVar.errors.forbiddenName">
        名字不能包含bob
      </div>
      <div *ngIf="nameVar.errors.nameValidator">
        自定义规则
      </div>
</div>

// 每一个表单控件都有valid/invalid(有效/无效)属性,为true说明满足验证条件
// *ngIf="name.invalid && (name.dirty || name.touched)"为true说明name的input框验证不通过,
// 并且dirty为true说明控件是输入过值的,而当控件失去焦点时,就会改变控件的 touched(碰过)状态。
// 确保用户还没有编辑过表单时不显示错误提示

自定义验证函数

a.在响应式表单中自定义验证器并使用



1. 新建xx.ts文件,用来写自定义验证器
import { AbstractControl } from "@angular/forms";
export function nameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): {
  [key: string]: any} | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {'forbiddenName': {value: control.value}} : null;
  };
}
2.使用

import { nameValidator } from '../../上面定义验证器函数的文件名'; 

//在constructor中定义表单控件实例,
//并用内置验证器和自定义验证器始初化
  this.myForm = this.fb.group({
  // 同步验证器
   name: ['', [
      Validators.required, //必填
      Validators.minLength(4), //最少4个字
      forbiddenNameValidator(/bob/i) // 禁止输入的名字中有bob
      nameValidator(/bob/i) // 自定义验证函数
    ]],
   age: ['', ageValidator()],
  })

//一定要加上这个getter属性,这样在模板中才能取到name,下面模板中name.errors中的name就是通过这个getter方法取到的
  get nameVar() { 
    return this.myForm.get('name');
  }
  
b.在模板驱动表单中自定义验证器并使用

1. 新建xx.ts文件,用来写自定义验证器
// 在模板驱动表单中,不能直接访问 FormControl 实例。
// 所以不能像响应式表单中那样把验证器传进去,而应该在模板中添加一个指令。
@Directive({
  selector: '[appForbiddenName]',
  providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
})
export class ForbiddenValidatorDirective implements Validator {
  @Input('appForbiddenName') forbiddenName: string;

  validate(control: AbstractControl): {[key: string]: any} | null {
    return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
                              : null;
  }
}
2.使用
import { appForbiddenName } from '../../上面定义验证器函数的文件名'; 

<input id="name" name="name" class="form-control"
      required minlength="4" appForbiddenName="bob"
      [(ngModel)]="hero.name" #name="ngModel" >
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>