Angular 4.x 中有两种表单:

  • Template-Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 )

  • Reactive Forms - 响应式表单

本文主要介绍 Template-Driven Forms (模板驱动式表单) ,将涉及 ngFormngModelngModelGroup、表单提交事件、表单验证和异常信息输出等内容。

User interface

// signup.interface.ts
export interface User {
name: string;
account: {
email: string;
confirm: string;
}
}

使用 template-driven 表单前,我们必须在 @NgModule 中导入 @angular/forms 库中的 FormModule

import { FormsModule } from '@angular/forms';

@NgModule({
imports: [
...,
FormsModule
],
declarations: [...],
bootstrap: [...]
})
export class AppModule {}

Template-driven approach

import { Component } from '@angular/core';
import { User } from './signup.interface'; @Component({
selector: 'signup-form',
template: `
<form novalidate>...</form>
`
})
export class SignupFormComponent {
public user: User = {
name: '',
account: {
email: '',
confirm: ''
}
};
constructor() {}
}

初始化 SignupFormComponent 组件类中的用户模型后,我们开始实现第一个功能点:即绑定 name、email、confirm 输入框的值

Binding ngForm and ngModel

我们从 ngForm 开始,更新后的模板如下:

<form novalidate #f="ngForm">
<label>
<span>Full name</span>
<input type="text" placeholder="Your full name">
</label>
</form>

上面代码中,我们把 ngForm 的值赋值给 #f 变量,通过该变量我们可以方便的获取表单的值。

友情提示:#f 变量的值,是 ngForm 指令的导出对象。novalidate属性是HTML5中的新属性,是一个布尔属性,规定当提交表单时不使用浏览器默认的规则对表单数据(输入)进行验证。

在模板中,我们可以通过以下方式查看表单的值:

{{ f.value | json }} // {}

上面示例 f.value 输出 {},因为此时我们表单中还未绑定任何值。在 Angular 1.x 中我们可以使用 ng-model 指令进行表单数据的双向绑定,接下来我们来看一下 Angular 4.x 中怎么实现数据绑定。

ngModel,[ngModel] and [(ngModel)]

在 Angular 4.x 中 ngModel 有三种不同的语法:

  • ngModel - 直接使用 ngModel 指令,没有使用绑定或关联任何值。此时,ngModel 将自动关联表单控件的 name 属性,并使用该值作为 ngForm 对象的属性名。

<form novalidate #f="ngForm">
...
<input
type="text"
placeholder="Your full name"
name="name"
ngModel>
...
</form>

友情提示:上面示例中,如果 input 输入框若未设置 name 属性,应用将会抛出异常。ngModel 指令基于输入框的 name 属性,进行绑定。

运行以上代码,f.value 的输入值如下:

{{ f.value | json }} // { name: '' }

非常好,我们已经绑定了 name 输入框的值。但我们应该怎么为输入框设置初始值?

[ngModel] = one-way binding syntax (单向绑定语法)

为了设置输入框初始值,我们先要更新一下 SignupFormComponent 组件类的用户模型:

...
user: User = {
name: 'Semlinker',
account: {
email: '',
confirm: ''
}
};
...

更新完用户模型,我们需要同步更新组件模板,具体如下:

<form #f="ngForm">
...
<input
type="text"
placeholder="Your full name"
name="name"
[ngModel]="user.name">
...
</form>

代码重新运行后,f.value 的输出如下:

{{ f.value | json }} // { name: 'Semlinker' }

从上面示例可以看出,使用 [ngModel] 允许我们通过 this.user.name 设置 name 输入框的初始值,而且该值会自动绑定到 f.value 对象上。

友情提示:[ngModel] 是单向绑定,当表单中 name 输入框的值改变时,不会同步更新 this.user.name

如果想在 name 输入框值变化时,自动同步更新 this.user.name 的值,我们需要使用双向绑定。

  • [(ngModel)] = two-way binding syntax (双向绑定),具体示例如下:

<form #f="ngForm">
...
<input
type="text"
placeholder="Your full name"
name="name"
[(ngModel)]="user.name">
...
</form>

上面示例成功运行后,我们可以在模板中新增以下代码,然后观察 user 模型的值:

{{ user | json }} // { name: 'Semlinker' }

需要注意的是,以下两种方式是等价的:

<input [(ngModel)]="user.name">
<input [ngModel]="user.name" (ngModelChange)="user.name = $event">

其中 [(ngModel)] 是简写的语法糖。

ngModels and ngModelGroup

我们已经介绍了 ngForm 和 ngModel 的基础用法,现在我们来完善剩下的内容。SignupFormComponent 组件类的用户模型中,包含了一个嵌套属性 account ,account 对象中包含 email 和 confirm 属性,分为表示邮件地址和重复确认的邮件地址。针对这种场景,Angular 4.x 为我们提供了 ngModelGroup 指令,具体示例如下:

<form novalidate #f="ngForm">
<label>
<span>Full name</span>
<input
type="text"
placeholder="Your full name"
name="name"
ngModel>
</label>
<div ngModelGroup="account">
<label>
<span>Email address</span>
<input
type="email"
placeholder="Your email address"
name="email"
ngModel>
</label>
<label>
<span>Confirm address</span>
<input
type="email"
placeholder="Confirm your email address"
name="confirm"
ngModel>
</label>
</div>
<button type="submit">Sign up</button>
</form>

使用 ngModelGroup 指令后,我们的 DOM 结构将更加合理:

ngForm -> '#f'
ngModel -> 'name'
ngModelGroup -> 'account'
-> ngModel -> 'email'
-> ngModel -> 'confirm'

以上代码成功运行后,浏览器中页面显示的结果:

// { name: 'Semlinker', account: { email: '', confirm: '' } }
{{ f.value | json }}

此时我们已经完成了表单数据绑定,接下来我们来为表单增加提交逻辑。

Template-driven submit

Angular 表单中提供了 ngSubmit 输出属性,用于监听表单的提交事件:

<form novalidate (ngSubmit)="onSubmit(f)" #f="ngForm">
...
</form>

当用户提交表单时,我们将会把 f 作为参数,调用 ngSubmit 关联的 onSubmit() 方法。onSubmit() 方法的具体实现如下:

export class SignupFormComponent {
user: User = {...};
onSubmit({ value, valid }: { value: User, valid: boolean }) {
console.log(value, valid);
}
}

上面代码中,我们使用 Object destructuring (对象解构) 的方式,从#f 引用对象中获取 value 和 valid 属性的值。其中 value 的值,就是 f.value 的值。表单的数据绑定方式和提交逻辑已经介绍完了,是该介绍表单实际应用中,一个重要的环节 — 表单验证。

Template-driven error validation

在为表单项添加验证规则前,我们先来更新一下 SignupFormComponent 组件中的 Sign up 按钮,确保在表单验证不通过时,不允许用户执行表单提交操作。更新后的代码如下:

<form novalidate (ngSubmit)="onSubmit(f)" #f="ngForm">
...
<button type="submit" [disabled]="f.invalid">Sign up</button>
</form>

以上代码我们通过 f.invalid 获取表单当前的验证状态 (验证不通过时该值为true),来控制按钮的 disabled 属性。

接下来开始进入正题,为表单添加验证规则:

<form novalidate #f="ngForm">
<label>
...
<input
...
ngModel
required>
</label>
<div ngModelGroup="account">
<label>
...
<input
...
name="email"
ngModel
required>
</label>
<label>
...
<input
...
name="confirm"
ngModel
required>
</label>
</div>
<button type="submit">Sign up</button>
</form>

#f 引用对象中有一个 controls 属性,通过该属性,我们就可以获取表单控件的验证信息,下面示例演示了如何获取 name 表单控件验证的异常信息:

<form novalidate #f="ngForm">
{{ f.controls.name?.errors | json }}
</form>

f.controls.name?.errors 的值是 null 或 undefined 时,表示验证成功。

友情提示:?.prop 称为安全导航操作符,用于告诉 Angular prop 的值可能不存在。

接下来为我们的 name 表单控件,添加显示异常信息的代码:

<div *ngIf="f.controls.name?.required" class="error">
Name is required
</div>

虽然我们已经可以获取某个表单项的验证信息,但有没有觉得使用 f.controls.name?.errors 这种方式,太麻烦了。那么有没有更简单的方式呢?我的答案是 - Yes !废话不多说,马上看示例:

<label>
...
<input
...
#userName="ngModel"
required>
</label>
<div *ngIf="userName.errors?.required" class="error">
Name is required
</div>

(备注:此处一定要使用 #userName="ngModel")

以上代码成功运行后,我们在浏览器中看到了异常信息,为了避免一开始就显示异常信息,我们可以更新一下 *ngIf 表达式的验证逻辑:

<div *ngIf="userName.errors?.required && userName.touched"
class="error">
Name is required
</div>

除了使用 required 验证规则之外,我们还可以使用 minlength (最小长度)、maxlength (最大长度) 等验证规则,下面我们继续来完善 SignupFormComponent 组件的功能,即为其它的表单控件添加显示异常信息的功能:

<!-- name -->
<div *ngIf="userName.errors?.required && userName.touched"
class="error">
Name is required
</div>
<div *ngIf="userName.errors?.minlength && userName.touched"
class="error">
Minimum of characters
</div> <!-- account: { email, confirm } -->
<div *ngIf="userEmail.errors?.required && userEmail.touched"
class="error">
Email is required
</div>
<div *ngIf="userConfirm.errors?.required && userConfirm.touched"
class="error">
Confirming email is required
</div>

我们通过使用模板变量的方式,为 account 表单组添加了显示验证异常信息的功能。但有没有其它更好的方式呢?有没有办法去掉 userEmail 和 userConfirm 引用对象呢?答案是肯定的,具体示例如下:

<div ngModelGroup="account" #userAccount="ngModelGroup">
<label>
<span>Email address</span>
<input
type="email"
placeholder="Your email address"
name="email"
ngModel
required>
</label>
<label>
<span>Confirm address</span>
<input
type="email"
placeholder="Confirm your email address"
name="confirm"
ngModel
required>
</label>
<div *ngIf="userAccount.invalid && userAccount.touched" class="error">
Both emails are required
</div>
</div>

(备注:记得移除模板上的 #userEmail 和 #userConfirm 引用哈)

表单控件的状态除了 touched 外,还包含其它几种状态?

表单控件有以下 6 种状态,我们可以通过 #userName="ngModel" 方式获取对应的状态值。具体状态如下:

  • valid - 表单控件有效

  • invalid - 表单控件无效

  • pristine - 表单控件值未改变

  • dirty - 表单控件值已改变

  • touched - 表单控件已被访问过

  • untouched - 表单控件未被访问过

表单控件上 #userName 和 #userName="ngModel" 这两种方式有什么区别?

  • #userName - 指向 input 表单控件

  • #userName="ngModel" - 指向 NgModel 实例

ngModelGroup 有什么作用?

ngModelGroup 指令是 Angular 提供的另一特殊指令,可以对表单输入内容进行分组,方便我们在语义上区分不同性质的输入。例如联系人的信息包括姓名及住址,现在需对姓名和住址进行精细化信息收集,姓名可精细化成姓和名字,地址可精细化成城市、区、街等。此时就可以将姓名及住址进行分组收集,具体如下:

<form #concatForm = "ngForm">
<fieldset ngModelGroup="nameGroup" #nameGroup="ngModelGroup">
<label>姓:</label>
<input type="text" name="firstname" [(ngModel)]="curContact.firstname"
required>
<label>名字:</label>
<input type="text" name="lastname" [(ngModel)]="curContact.lastname"
required>
</fieldset>
<fieldset ngModelGroup="addressGroup" #addressGroup ="ngModelGroup">
<label>街:</label>
<input type="text" name="street" [(ngModel)]="curContact.street" required> <label>区:</label>
<input type="text" name="zip" [(ngModel)]="curContact.zip" required>
<label>城市:</label>
<input type="text" name="city" [(ngModel)]="curContact.city" required>
</fieldset>
</form>

上述例子分别对联系人的姓名和住址进行分组, ngModelGroup 将姓和名字的表单内容进行包裹组成姓名分组,将城市、区和街道的表单内容进行包裹组成住址分组。此时concatForm.value值为:

{
nameGroup: {
firstname: '',
lastname: '',
},
addressGroup: {
street: '',
zip: '',
city: ''
}
}

转自:https://segmentfault.com/a/1190000009037539

Template-Driven Forms 模板驱动式表单的更多相关文章

  1. Angular Reactive Forms -- Model-Driven Forms响应式表单

    Angular 4.x 中有两种表单: Template-Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 )  官方文档:https://v2.angul ...

  2. ng2响应式表单-翻译与概括官网REACTIVE FORMS页面

    本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...

  3. Angular2响应式表单-翻译与概括官网REACTIVE FORMS页面

    本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...

  4. Angular2响应式表单

    本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...

  5. Angular11 模板表单、响应式表单(自定义验证器)、HTTP、表单元素双向绑定

    1 模板表单 模型通过指令隐式创建 技巧01:需要在模块级别引入 FormsModule ,通常在共享模块中引入再导出,然后在需要用到 FormsModule 的模块中导入共享模块就可以啦 impor ...

  6. angular2-响应式表单

    响应式表单是同步的.模板驱动表单是异步的.这个不同点很重要 使用响应式表单,我们会在代码中创建整个表单控件树. 我们可以立即更新一个值或者深入到表单中的任意节点,因为所有的控件都始终是可用的. 模板驱 ...

  7. angular6的响应式表单

    1:在AppModule模块里面引入 ReactiveFormsModule 要使用响应式表单,就要从@angular/forms包中导入ReactiveFormsModule,并把它添加到你的NgM ...

  8. flask 在模板中渲染表单

    在模板中渲染表单 为了能够在模板中渲染表单,我们需要把表单类实例传入模板.首先在视图函数里实例化表单类LoginForm,然后再render_template()函数中使用关键脑子参数form将表单实 ...

  9. flask用宏渲染表单模板时,表单提交后,如果form.validate_on_submit()返回的是false的可能原因

    flask用宏渲染表单模板时,表单提交后,提交的内容符合DataRequired()校验, 但是form.validate_on_submit()返回的是False, 原因可能是表单模板中的<f ...

随机推荐

  1. HtmlAnchor点击之后保持高亮

    HtmlAnchor点击之后保持高亮,就是一个链接,在点击之后,还要保持高亮状态.应用在网站后台管理界面,左边菜单点击之后,菜单保持点击高亮状态.为了实现这个功能,确实花上Insus.NET不少时间. ...

  2. Centos7环境下 安装ffmage2.7.1过程

    参考http://trac.ffmpeg.org/wiki/CompilationGuide/Centos#FFmpeg 先查看了官方文档,消化以后,开始自己编译ffmage2.7.1,所需要安装包, ...

  3. firefox并不支持selectSingleNode和selectNodes的解决方法

    转自:http://qsrock.iteye.com/blog/209585 function test(){ var perid = document.thisForm.PerID.value; v ...

  4. 标准Trie字典树学习一:原理解析

    特别声明: 博文主要是学习过程中的知识整理,以便之后的查阅回顾.部分内容来源于网络(如有摘录未标注请指出).内容如有差错,也欢迎指正! 系列文章: 1. 字典树Trie学习一:原理解析 2.字典树Tr ...

  5. 从JSON中读取数据追加到HTML中

    本文来自https://www.jianshu.com/p/04127d74d88c,并非本人原创,只是作为自己学习使用的资料,如有浏览者请点击地址自行到原作者页面浏览 有时候我们需要将json数据直 ...

  6. tensorboard实现tensorflow可视化

    1.工程目录 2.data.input_data.py的导入 在tensorflow更新之后可以进行直接的input_data的导入 # from tensorflow.examples.tutori ...

  7. COGS2217 papertask

    传送门 以前看到这题的时候觉得是道好题啊……然而今天没多久就做出来了= =(装B 表示并没有看懂其他人写的是什么做法,感觉我的做法好奇怪…… 我的做法是这样的: 首先给括号配对,不难发现所有括号串要么 ...

  8. iframe在移动端的缩放

    工作中碰到个奇怪的问题,折腾了大半天,终于算是解决了,这里把分析思路和解决办法记录下. 项目是做响应式的公司官网,前期的静态图页面切完后就提交给后台作为模板使用了,我也就基本退出项目. 在后端落地时发 ...

  9. Reporting Service 2005 迁移 到 2008

    1. 备份ReportServer 和ReportServerTempDB 的数据库. 2. 在Reporting Services Configuration Manager 中备份Encrypti ...

  10. Maximum Subarray 连续子数组最大和

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...