在企业应用开发时,表单是一个 躲不过去的事情,和面向消费者的应用不同,企业领域的开发中,表单的使用量是惊人的。这些表单的处理其实是一个 挺复杂的事情,比如有的是涉及到多个 Tab 的表单,有的是向导形式多个步骤的,各种复杂的验证逻辑和时不时需要弹出的对话框等等。笔者试图在这 一系列文章 中对 Angular 中的表单处理做一个 相对完整的梳理。
Angular 中提供两种类型的表单处理机制,一种叫模版驱动型(Template Driven)的表单,另一种叫模型驱动型表单( Model Driven ),这后一种也叫响应式表单 ( Reactive Forms ),由于模版驱动中有一个 ngModel 的指令,容易和这里说的模型驱动混淆,所以在我们的文章 中叫后一种说法:响应式表单。
第一篇主要介绍模版驱动型的表单。
号外
本文评论 区会抽出5位童鞋,赠送笔者的 《Angular 从零到一》纸书,机不可失,大家踊跃发言哦。
模版驱动的表单
模版驱动的表单和 AngularJS 对于表单的处理类似,把一些指令(比如 ngModel )、数据值和行为约束(比如 require 、 minlength 等等)绑定到模版中(模版就是组件元数据 @Component 中定义的那个 template ),这也是模版驱动这个叫法的来源。总体来说,这种类型的表单通过绑定把很多工作交给了模版。
模版驱动的例子
还是用例子来说话,比如我们有一个 用户 注册 的表单,用户名 就是 email ,还需要填的信息有:住址、密码和重复密码。这个应该是比较常见的一个 注册 时需要的信息了。那么我们第一步来建立领域模型:
rush:js;">
// src/app/domain/index.ts
export interface User {
// 新的
用户 id一般由服务器
自动 生成 ,所以可以为空,用 ? 标示
id?: string;
email: string;
password: string;
repeat: string;
address: Address;
}
export interface Address {
province: string; // 省份
city: string; // 城市
area: string; // 区县
addr: string; // 详细地址
}
接下来我们建立模版文件 ,一个 最简单的 HTML 模版,先不增加 任何的绑定或事件处理:
渲染之后的效果 就像下面这样:
简单的Form
数据绑定
对于模版驱动型的表单处理,我们首先需要在对应的模块中引入 FormsModule ,这一点千万不要忘记了。
rush:js;">
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from "@angular/forms";
import { TemplateDrivenComponent } from './template-driven/template-driven.component';
@NgModule({
imports: [
CommonModule,FormsModule
],exports: [TemplateDrivenComponent],decl
ara tions: [TemplateDrivenComponent]
})
export class FormDemoModule { }
进行模版驱动类型的表单处理的
一个 必要步骤就是建立数据的双向绑定,那么我们需要在组件中建立
一个 类型为 User 的成员变量并赋初始值。
// template-driven.component.ts
// 省略元数据和导入的类库信息
export class TemplateDrivenComponent implements OnInit {
user: User = {
email: '',password: '',repeat: '',address: {
province: '',city: '',area: '',addr: ''
}
};
// 省略其他部分
}
有了这样一个 成员变量之后,我们在组件模版中就可以使用 ngModel 进行绑定了。
令人困惑的 ngModel
我们在 Angular 中可以使用三种形式的 ngModel 表达式: ngModel,[ngModel] 和 [(ngModel)] 。但无论那种形式,如果你要使用 ngModel 就必须为该控件(比如下面的 input )指定一个 name 属性 ,如果你忘记添加 name 的话,多半你会看到下面这样的错误 :
ERROR Error: Uncaught (in promise): Error: If ngModel is used within a form tag,either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions.
ngModel 和 FormControl
假如我们使用的是 ngModel ,没有任何中括号小括号的话,这代表着我们创建了一个 FormControl 的实例,这个实例将会跟踪值的变化、用户 的交互、验证状态以及保持视图和领域对象的同步等工作。
rush:js;">
如果我们将这个控件放在一个 Form 表单中, ngModel 会自动 将这个 FormControl 注册 为 Form 的子控件。下面的例子中我们在
rush:js;">
ovalidate #f="ngForm">
电子邮件 地址
required
pattern="([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,4}">
密码
required
minlength="8">
确认密码
required
minlength="8">
disabled]="f.invalid">注册
Angular 中有几种内建支持 的验证器( Validators )
required - 需要 FormControl 有非空值
minlength - 需要 FormControl 有最小长度的值
maxlength - 需要 FormControl 有最大长度的值
pattern - 需要 FormControl 的值可以匹配正则表达式
如果我们想看到结果的话,我们可以在模版中加上 下面的代码 ,将错误 以 JSON 形式输出 即可。
rush:js;">
email 验证: {{f.controls.email?.errors | json}}
我们看到,如果不填电子邮件 的话,错误 的 JSON 是 {"required ": true} ,这告诉我们目前有一个 required 的规则没有被满足。
验证结果
当我们输入一个 字母 w 之后,就会发现错误 变成了下面的样子。这是因为我们对于 email 应用了多个规则,当必填项满足后,系统会继续检查其他验证结果。
rush:js;">
{
"pattern":
{
"
required Pattern": "^([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,4}$","actualValue": "w"
}
}
通过几次实验,我们应该可以得出结论,当验证未通过时,验证器返回的是一个 对象, key 为验证的规则(比如 required ,minlength 等),value 为验证结果。如果验证通过,返回的是一个 null 。
知道这一点后,我们其实就可以做出验证出错的提示 了,为了方便引用,我们还是导出 ngModel 到一个 email 引用,然后就可以访问这个 FormControl 的各个属性 了:验证的状态( valid/invalid )、控件的状态(是否获得过焦点 -- touched/untouched,是否更改过内容 -- pristine/dirty 等)
rush:js;">
电子邮件 地址
required && email.touched" class="error">
email 是必填项
内建的验证器对于两个密码比较的这种验证是不够的,那么这就需要我们自己定义一个 验证器。对于响应式表单来说,会比较简单一些,但对于模版驱动的表单,这需要我们实现一个 指令来使这个验证器更通用和更一致。因为我们希望实现的样子应该是和 required 、 minlength 等差不多的形式,比如下面这个样子 validateEqual="repeat"
那么要实现这种形式的验证的话,我们需要建立一个 指令,而且这个指令应该实现 Validator 接口。一个 基础的框架如下:
RepeatValidatorDirective),multi: true
}
]
})
export class RepeatValidatorDirective implements Validator{
constructor() { }
validate(c: AbstractControl): { [key: string]: any } {
return null;
}
}
我们还没有开始正式的写验证逻辑,但上面的框架已经出现了几个有意思的点:
1.Validator 接口要求必须实现的一个 方法 是 validate(c: AbstractControl): ValidationErrors | null; 。这个也就是我们前面提到的验证正确返回 null 否则返回一个 对象,虽然没有严格的约束,但其 key 一般用于表示这个验证器的名字或者验证的规则名字,value 一般是失败的原因或验证结果。
2.和组件类似,指令也有 selector 这个元数据,用于选择那个元素应用该指令,那么我们这里除了要求 DOM 元素应用 validateEqual 之外,还需要它是一个 ngModel 元素,这样它才是一个 FormControl,我们在 validate 的时候才是合法的。
3.那么那个 providers 里面那些面目可憎的家伙又是干什么的呢? Angular 对于在一个 FormControl 上执行验证器有一个 内部机制: Angular 维护一个 令牌为 NG_VALIDATORS 的 multi provider (简单来说,Angular 为一个 单一令牌注入多个值的这种形式叫 multi provider )。所有的内建验证器都是加到这个 NG_VALIDATORS 的令牌上的,因此在做验证时,Angular 是注入了 NG_VALIDATORS 的依赖,也就是所有的验证器,然后一个 个的按顺序执行。因此我们这里也把自己加到这个 NG_VALIDATORS 中去。
4.但如果我们直接写成 useExisting: RepeatValidatorDirective 会出现一个 问题, RepeatValidatorDirective 还没有生成 ,你怎么能在元数据中使用呢?这就需要使用 forwardRef 来解决 这个问题,它接受一个 返回一个 类的函数 作为参数,但这个函数 不会立即被调用 ,而是在该类声明后被调用 ,也就避免了 undefined 的状况。
下面我们就来实现这个验证逻辑,由于密码和确认密码有主从关系,并非完全的平行关系。也就是说,密码是一个 基准对比对象,当密码改变时,我们不应该提示 密码和确认密码不符,而是应该将错误 放在确认密码中。所以我们给出另一个 属性 reverse 。
rush:js;">
export class RepeatValidatorDirective implements Validator{
constructor(
@Attribute('validateEqual') public validateEqual: string,@Attribute('reverse') public reverse: string) { }
private get isReverse() {
if (!this.reverse) return false;
return this.reverse === 'true' ? true: false;
}
validate(c: AbstractControl): { [key: string]: any } {
// 控件自身值
let self = c.value;
// 要对比的值,也就是在 validateEqual=“ctrlname” 的那个控件的值
let target = c.root.get(this.validateEqual);
// 不反向
查询 且值不相等
if (target && self !== target.value && !this.isReverse) {
return {
validateEqual: true
}
}
// 反向
查询 且值相等
if (target && self === target.value && this.isReverse) {
delete target.errors['validateEqual'];
if (!Object.keys(target.errors).length) target.setErrors(null);
}
// 反向
查询 且值不相等
if (target && self !== target.value && this.isReverse) {
target.setErrors({
validateEqual: true
})
}
return null;
}
}
这样改造后,我们的模版文件 中对于密码和确认密码的验证器如下:
rush:js;">
required
minlength="8"
validateEqual="repeat"
reverse="true">
required
minlength="8"
validateEqual="password"
reverse="false">
完成后的验证错误 提示
表单的提交
表单的提交比较简单,绑定表单的 ngSubmit 事件即可
rush:js;">
ovalidate #f="ngForm" (ngSubmit)="onSubmit(f,$event)">
但需要注意的一点是,button如果不指定类型的话,会被当做 type="submit" ,所以当按钮不是进行提交表单的话,需要显式指定 type="button" 。而且如果遇到点击提交按钮页面 刷新的情况的话,意味着默 认的表单提交事件引起了浏览器的刷新,这种时候需要阻止事件冒泡。
rush:js;">
onSubmit({value,valid},event: Event){
if(valid){
console.log(value);
}
event.preventDefault();
}
对于模板驱动的表单,我们就先总结到这里,下一篇 文章 我们会一起讨论响应式表单。
本文代码 : nofollow " href="https://github.com/wpcfan/ng-features.git">https://github.com/wpcfan/ng-features.git
以上所述是小编给大家介绍的Angular 2+ 的表单(一)之模板驱动型表单。编程之家 jb51.cc 收集整理的教程希望能对你有所帮助,如果觉得编程之家不错,可分享 给好友!感谢支持 。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。