当前位置: 移动技术网 > IT编程>脚本编程>AngularJs > Angular.js 4.x中表单Template-Driven Forms详解

Angular.js 4.x中表单Template-Driven Forms详解

2018年04月30日  | 移动技术网IT编程  | 我要评论
angular 4.x 中有两种表单: template-driven forms - 模板驱动式表单 (类似于 angular 1.x 中的表单 ) re

angular 4.x 中有两种表单:

  • template-driven forms - 模板驱动式表单 (类似于 angular 1.x 中的表单 )
  • reactive forms - 响应式表单

本文主要介绍 template-driven forms (模板驱动式表单) ,将涉及 ngform、ngmodel、ngmodelgroup、表单提交事件、表单验证和异常信息输出等内容。

contents

  • ngmodule and template-driven forms
  • binding ngform and ngmodel
  • ngmodel,[ngmodel] and [(ngmodel)]
  • ngmodels and ngmodelgroup
  • template-driven submit
  • template-driven error validation

form base and interface

form base

<form novalidate>
 <label>
 <span>full name</span>
 <input
 type="text"
 name="name"
 placeholder="your full name">
 </label>
 <div>
 <label>
 <span>email address</span>
 <input
 type="email"
 name="email"
 placeholder="your email address">
 </label>
 <label>
 <span>confirm address</span>
 <input
 type="email"
 name="confirm"
 placeholder="confirm your email address">
 </label>
 </div>
 <button type="submit">sign up</button>
</form>

接下来我们要实现的功能如下:

  • 绑定 name、email、confirm 输入框的值
  • 为所有输入框添加表单验证功能
  • 显示验证异常信息
  • 表单验证失败时,不允许进行表单提交
  • 表单提交功能

user interface

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

ngmodule and template-driven forms

在我们继续深入介绍 template-driven 表单前,我们必须在 @ngmodule 中导入 @angular/forms 库中的 formmodule:

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

@ngmodule({
 imports: [
 ...,
 formsmodule
 ],
 declarations: [...],
 bootstrap: [...]
})
export class appmodule {}

友情提示:若使用 template-driven 表单,则导入 formsmodule;若使用 reactive forms,则导入 reactiveformsmodule。

template-driven approach

使用模板驱动的表单,我们基本上可以将组件类留空,直到我们需要读取/写入值 (例如提交和设置初始值)。我们将基于上面的定义的基础表单,创建 signupformcomponent :

signup-form.component.ts

import { component } from '@angular/core';

@component({
 selector: 'signup-form',
 template: `
 <form novalidate>...</form>
 `
})
export class signupformcomponent {
 constructor() {}
}

这是一个很基础的组件,接下来我们导入之前定义的 user 接口,具体如下:

import { user } from './signup.interface';

@component({...})
export class signupformcomponent {
 public user: user = {
 name: '',
 account: {
 email: '',
 confirm: ''
 }
 };
}

初始化 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 指令的导出对象。

@directive({
 selector: 'form:not([ngnoform]):not([formgroup]),ngform,[ngform]',
 providers: [formdirectiveprovider],
 host: {'(submit)': 'onsubmit($event)', '(reset)': 'onreset()'},
 outputs: ['ngsubmit'],
 exportas: 'ngform'
})
export class ngform extends controlcontainer implements form {}

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

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

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

ngmodel,[ngmodel] and [(ngmodel)]

在 angular 4.x 中 ngmodel 有三种不同的语法:

1、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 输入框的值。但我们应该怎么为输入框设置初始值?

2、[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 的值,我们需要使用双向绑定。

3、[(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>

上面代码中,我们为每个 input 表单控件,添加了 required (必填项) 的验证规则。一切都那么简单,剩下的问题就是如何获取验证失败的异常消息。

皇上,您还记得当年大明湖畔的夏雨荷吗? — no,no,no !我只记得安谷拉 (angular) 湖畔的美女 (f)。

#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 2 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 实例

ngmodel 指令

// angular2/packages/forms/src/directives/ng_model.ts 片段
@directive({
 selector: '[ngmodel]:not([formcontrolname]):not([formcontrol])',
 providers: [formcontrolbinding],
 exportas: 'ngmodel' // // 导出指令实例,使得可以在模板中调用
})
export class ngmodel extends ngcontrol implements onchanges, ondestroy {
}

ngcontrol 抽象类

// angular2/packages/forms/src/directives/ng_control.ts 片段
export abstract class ngcontrol extends abstractcontroldirective {
 /** @internal */
 _parent: controlcontainer = null;
 name: string = null;
 valueaccessor: controlvalueaccessor = null;
 ...
 abstract viewtomodelupdate(newvalue: any): void;
}

abstractcontroldirective 抽象类

// angular2/packages/forms/src/directives/abstract_control_directive.ts 片段
export abstract class abstractcontroldirective {
 get valid(): boolean { return this.control ? this.control.valid : null; }

 get invalid(): boolean { return this.control ? this.control.invalid : null; }

 get errors(): validationerrors | null { return this.control ? 
 this.control.errors : null; }

 get pristine(): boolean { return this.control ? this.control.pristine : null; }

 get dirty(): boolean { return this.control ? this.control.dirty : null; }

 get touched(): boolean { return this.control ? this.control.touched : null; }

 get untouched(): boolean { return this.control ? this.control.untouched : null; }

 get valuechanges(): observable<any> { return this.control ? 
 this.control.valuechanges : null; }

 haserror(errorcode: string, path: string[] = null): boolean {
 return this.control ? this.control.haserror(errorcode, path) : false;
 }

 geterror(errorcode: string, path: string[] = null): any {
 return this.control ? this.control.geterror(errorcode, path) : null;
 }
}

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: ''
 } 
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网