From表单分为两种类型:
- 模板驱动表单(Template-Driven Forms)
- 模型驱动表单(Model-Driven-Forms)
模型:有结构的状态(是一种数据结构)FormContrller 表单项
@angular/forms
模板驱动表单
@angular/reactiveForms
关注点是表单的行为,不是怎么生成DOM。关注点是出错了,是一种出错的状态,是否发生了改变。
Template-Driven Forms
- 知识点1
Template
驱动表单时,因为模板驱动的表单有它自己的模块,所以我们得把FormsModule
添加到应用的imports
数组中去,这样我们才能使用表单。
注意:
如果一个组件、指令或者管道出现在模块的
imports
数组中,就说明它是外来模块,不要在到declarations
数组中声明它们。
如果你自己写的它,并且它属于当前模块,就要把它声明在declarations
数组中。
- 知识点2 - 表单
<div class="container">
<h1>Hero Form</h1> {{diagnostic}} <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name"> TODO:remove this: {{model.name}} </div> <div class="form-group"> <label for="alterEgo">Alter Ego</label> <input type="text" class="form-control" id="laterEgo" [(ngModel)]="model.alterEgo" name="alterEgo"> </div> <div class="form-group"> <label for="power">Hero Power</label> <select class="form-control" id="power" required [(ngModel)]="model.power" name="power"> <option *ngFor="let p of powers" [value]="p">{{p}}</option> </select> </div> </form> </div>
知识点3 - [(ngModel)]内幕
- [] ,这是一个从模型到视图的单项数据绑定。
- () ,这是一个从视图到模型的反向数据绑定。
- [()] ,这是双向数据绑定和双向数据流。
- 我们可以把
ngModel
绑定拆成两个独立的绑定,就想我们重写的“Name”的<input>
绑定一样:
<input type="text" class="form-control" id="name" required [ngModel]="model.name" name="name" (ngModelChange)="model.name = $event" > TODO: remove this: {{model.name}}
这个属性绑定看起来很眼熟,但事件绑定看起来有点怪。
ngModelChange
并不是<input>
元素的事件。 它实际上是来自NgModel
指令的事件属性。 当 Angular 在表单中看到[(x)]的绑定目标时, 它会期待这个x
指令有一个名为x
的输入属性,和一个名为xChange
的输出属性。模板表达式中的另一个古怪之处是
model.name = $event
。 之前看到的$event
对象来自 DOM 事件。 但ngModelChange
属性不会生成 DOM 事件 —— 它是Angular EventEmitter类型的属性,当它触发时, 它返回的是输入框的值 —— 也正是希望赋给模型name
属性的值。很高兴知道这些,但是这样现实吗?实践上中,几乎总是优先使用
[(ngModel)]
形式的双向绑定。 只有当需要在事件处理函数中做一些特别的事情(例如合并或限制按键频率)时,才会拆分出独立的事件处理函数。知识点4 - 表单验证
在表单中使用
ngModel
,能让我们比仅仅使用双向数据绑定获得更多的控制权。eg:- 用户是否碰过此控件?
- 它值的变化?
- 数据是否有效?
ngModel
指令不仅仅跟踪状态,它还使用三个CSS类来更新控件,以反映当前的状态。
状态 | 为真时的CSS类 | 为假时的CSS类 |
---|---|---|
控件已经被访问过 | ng-touched | ng-untouched |
控件值已经变化 | ng-dirty | ng-pristine |
控件值是有效的 | ng-valid | ng-invalid |
我们可以在name的input
标签上添加一个名为spy的临时模板引用变量,然后利用这个spy
来显示它上面的所有css类。
<input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #spy >
<br>TODO: remove this: {{spy.className}} // 这样显示className
- 知识点5 - 表单验证
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" #spy="ngModel">
TODO:remove this: {{model.name}}| {{spy.className}}
</div>
<div [hidden]="spy.valid || spy.pristine" class="alter alter-danger">
Name IS required!
</div>
#spy=”ngModel”可以得到这个
input`的一些状态。
知识点6 - 表单重置
heroForm.reset()
可以将表单重置。知识点7 - 表单提交
在填表完成之后,用户还应该能提交这个表单。 “Submit(提交)”按钮位于表单的底部,它自己不做任何事,但因为有特殊的 type 值 (type=”submit”),所以会触发表单提交。
表单提交,我们使用
NgSubmit
,并且通过事件绑定把它绑定到HeroFormComponent.submit()方法。
<button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button>
NgForm指令
什么NgForm指令?之前没有添加过 NgForm 指令啊!是 Angular 干的。Angular 自动创建了NgForm指令,并把它附加到标签。NgForm指令为form元素扩充了额外的特性。 它持有通过ngModel指令和name属性为各个元素创建的那些控件,并且监视它们的属性变化,包括有效性。 它还有自己的valid属性,只有当其中所有控件都有效时,它才有效。
Model-Driven-Forms
有时候手动编写和维护表单所需工作量和时间会过大。特别是在需要编写大量表单时。表单都很相似,而且随着业务和监管需求的迅速变化,表单也要随之变化,这样维护的成本过高。
整个例子,我的理解就是:
所谓动态表单,也就是,将表单的配置信息写成配置方便更改,而不是以模板的形式写死,这样,我们就可以在不修改应用代码的情况下,动态的创建表单项。
程序启动
我们将使用响应式表单(Reactive Forms)。
响应式表单属于另外一个叫做ReactiveFormsModule的NgModule,所以,为了使用响应式表单类的指令,我们得往@angular/forms库中引入ReactiveFormsModule模块。并且需要引入imports: [ ReactiveFormsModule ],
。 (这一点很重要,不能忘记引入)
自定义表单
模板驱动表单的实现例子
pdf文档的名字:Custom Form Controls in Angular 2 by thoughtram.pdf
下面是一个使用表单的例子。
import { Component,forwardRef } from '@angular/core';
import { ControlValueAccessor,NG_VALUE_ACCESSOR } from '@angular/forms';
// 所谓表单指的就是,数据从model-->view,然后再从view-->model的这样的一些项,类似的有`input`,`select`,`textarea`等这样的。
@Component({
template: `
<input type="text" [(ngModel)]="value">
`,providers: [
{
provide: NG_VALUE_ACCESSOR,useExisting: forwardRef(() => CustomerFormItemComponent),multi: true,},],})
class CustomerFormItemComponent implements ControlValueAccessor {
private propagateChange: (_: any) => {};
private _value: String; // 为了避免出现死循环,所以设置一个_value
// 如果只设置get方法的话,可以设置只读
get value() {
return this._value;
}
// 这种写法是语法级别的,set方法可以很好的监听传入值的改变。
set value(value: String) {
if (this._value === value) {
return;
} else {
this._value = value;
this.propagateChange(value);
}
}
// 表单初始化的时候,完成model --> view 的数据设置。
writeValue(value: String): void {
this._value = value;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
}
未完待续…