angular 响应式表单和模型驱动表单—angular学习笔记4

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

  • 相同:两者都从视图中捕获用户输入事件、验证用户输入、创建表单模型、修改数据模型,并提供跟踪这些更改的途径。

  • 不同:主要是在如何处理和管理表单和表单数据方面有所不同。具体方面:

image.png
  • 响应式表单方法,需要显式创建 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" >

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351