Template-Driven Forms 模板驱动式表单
Angular 4.x 中有两种表单:
Template-Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 )
Reactive Forms - 响应式表单
本文主要介绍 Template-Driven Forms (模板驱动式表单) ,将涉及 ngForm、ngModel、ngModelGroup、表单提交事件、表单验证和异常信息输出等内容。
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 模板驱动式表单的更多相关文章
- Angular Reactive Forms -- Model-Driven Forms响应式表单
Angular 4.x 中有两种表单: Template-Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 ) 官方文档:https://v2.angul ...
- ng2响应式表单-翻译与概括官网REACTIVE FORMS页面
本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...
- Angular2响应式表单-翻译与概括官网REACTIVE FORMS页面
本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...
- Angular2响应式表单
本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...
- Angular11 模板表单、响应式表单(自定义验证器)、HTTP、表单元素双向绑定
1 模板表单 模型通过指令隐式创建 技巧01:需要在模块级别引入 FormsModule ,通常在共享模块中引入再导出,然后在需要用到 FormsModule 的模块中导入共享模块就可以啦 impor ...
- angular2-响应式表单
响应式表单是同步的.模板驱动表单是异步的.这个不同点很重要 使用响应式表单,我们会在代码中创建整个表单控件树. 我们可以立即更新一个值或者深入到表单中的任意节点,因为所有的控件都始终是可用的. 模板驱 ...
- angular6的响应式表单
1:在AppModule模块里面引入 ReactiveFormsModule 要使用响应式表单,就要从@angular/forms包中导入ReactiveFormsModule,并把它添加到你的NgM ...
- flask 在模板中渲染表单
在模板中渲染表单 为了能够在模板中渲染表单,我们需要把表单类实例传入模板.首先在视图函数里实例化表单类LoginForm,然后再render_template()函数中使用关键脑子参数form将表单实 ...
- flask用宏渲染表单模板时,表单提交后,如果form.validate_on_submit()返回的是false的可能原因
flask用宏渲染表单模板时,表单提交后,提交的内容符合DataRequired()校验, 但是form.validate_on_submit()返回的是False, 原因可能是表单模板中的<f ...
随机推荐
- JAVA泛型——基本使用
Java1.5版本推出了泛型,虽然这层语法糖给开发人员带来了代码复用性方面的提升,但是这不过是编译器所做的一层语法糖,在真正生成的字节码中,这类信息却被擦除了.笔者发现很多几年开发经验的程序员,依然不 ...
- C# System.Timers.Timers的用法在工控设备上位中的用法
这两天写设备的上位,由于要读取服务器上的数据库,通过WEBSERVICE访问数据库,我具体的做法是: 1.专门用Timer起线程执行,由于在用的时候报错,不能访问其他线程资源的错误,因此我用了委托的方 ...
- C#获取Url不同路径的方法大全
在 C# 中,用 Request 对象来获取 Url 的各种路径,包括上一页 Url.域名.绝对路径.相对路径和物理路径.虽然在某些情况下,无法获取到 Url,但可以获取到大部分情况下的 Url,下 ...
- SpringBoot MockMvc的单元测试
对于类的测试,可以有很多的方式进行实现,比如可以使用PostMan,使用HttpClient请求等,这里主要讲的是MockMcv的测试 一:引入依赖 <dependency> <gr ...
- Android中自定义弹出PopupWindow后返回按键失效的问题解决
PopupWindow出现之后,默认的是所有的操作都无效的,除了HOME键.而且是可以操作后面的界面的.想要锁定后面的界面,很简单,只需要让PopupWindow是focusable的:popupWi ...
- HTTP2 概述
HTTP/2,也就是超文本传输协议第2版,不论是1还是2,HTTP的基本语义是不变的,比如方法语义(GET/PUST/PUT/DELETE),状态码(200/404/500等),Range Reque ...
- LeetCode SQL: Second Highest Salary
, NULL, salary) as `salary` from ( ,) tmp Write a SQL query to get the second highest salary from th ...
- python学习之老男孩python全栈第九期_day027知识点总结——反射、类的内置方法
一. 反射 ''' # isinstance class A:pass class B(A):pass a = A() print(isinstance(a,A)) # 判断对象和类的关系 print ...
- 【零基础学习FreeRTOS嵌入式系统】之一:FreeRTOS环境搭建
[零基础学习FreeRTOS嵌入式系统]之一:FreeRTOS环境搭建 一:FreeRTOS系统下载 在官网上https://www.freertos.org/,找到下载入口. 或直接进入下载地址ht ...
- Cloudera Manager5及CDH5在线(cloudera-manager-installer.bin)安装详细文档
问题导读:1.Cloudera Manager5如何使用cloudera-manager-installer.bin安装?2.Cloudera Manager5安装被中断该如何继续安装?还是重新安装? ...