表单对于任何现代前端应用程序都是至关重要的,即使我们没有意识到,表单也是我们每天使用的功能。 需要使用表格来安全地将用户登录到应用程序,搜索特定城市中所有可用的酒店,预订出租车,创建待办事项列表,以及做很多我们惯用的其他事情。 某些表单只有几个输入字段,而其他表单可能具有一个扩展到几个页面或选项卡的字段数组。
在本教程中,我们将讨论可用于在Angular中开发表单的不同策略。 不管您选择哪种策略,表单库都应涵盖以下内容:
- 支持双向绑定,以便输入控制值与组件状态同步。
- 跟踪表单状态并使用视觉提示让用户知道当前状态是否有效。 例如,如果用户名包含无效字符,则用户名的输入字段周围应出现一个红色边框。
- 有一种机制可以正确显示验证错误。
- 启用或禁用表单的某些部分,除非满足某些验证条件。
Angular表单介绍
Angular是一个成熟的前端框架,它具有自己的一组库来构建复杂的表单。 Angular的最新版本具有两种强大的表单构建策略。 他们是:
- 模板驱动形式
- 模型驱动或反应形式
两种技术都属于@angular/forms
库,并且基于相同的表单控件类。 但是,它们的哲学,编程风格和技术差异很大。 一个选择另一个取决于您的个人品味,还取决于您尝试创建的表单的复杂性。 我认为,您应该首先尝试这两种方法,然后再选择一种适合您的风格和当前项目的方法。
本教程的第一部分将通过一个实际的例子介绍模板驱动的表单:构建一个对所有表单字段进行验证的注册表单。 在本教程的第二部分中,我们将追溯使用模型驱动的方法来创建相同表单的步骤。
模板驱动的表格
模板驱动的方法是从AngularJS时代借来的一种策略。 我认为,这是构建表单的最直接的方法。 它是如何工作的? 我们将使用一些Angular指令。
指令允许您将行为附加到DOM中的元素。
— Angular文档
Angular提供了特定于表单的指令,可用于绑定表单输入数据和模型。 特定于表单的指令为纯HTML表单添加了额外的功能和行为。 最终结果是模板负责与模型和表单验证绑定值。
在本教程中,我们将使用模板驱动的表单来创建应用程序的注册页面。 该表单将涵盖最常见的表单元素以及对这些表单元素的不同验证检查。 这是您将在本教程中遵循的步骤。
- 将FormsModule添加到
app.module.ts
。 - 为用户模型创建一个类。
- 为注册表单创建初始组件和布局。
- 使用Angular格式指令,例如
ngModel
,ngModelGroup
和ngForm
。 - 使用内置验证器添加验证。
- 有意义地显示验证错误。
- 使用
ngSubmit
处理表单提交。
让我们开始吧。
先决条件
该项目的代码可在我的GitHub存储库中找到。 下载zip或克隆存储库以查看其运行情况。 如果您想从头开始,请确保已安装Angular CLI。 使用ng
命令生成一个新项目。
$ ng new SignupFormProject
接下来,为SignupForm生成一个新组件。
ng generate component SignupForm
用以下内容替换app.component.html的内容:
<app-signup-form> </app-signup-form>
这是src /目录的目录结构。 我删除了一些不必要的文件,以使事情变得简单。
.
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── signup-form
│ │ ├── signup-form.component.css
│ │ ├── signup-form.component.html
│ │ └── signup-form.component.ts
│ └── User.ts
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── tsconfig.app.json
└── typings.d.ts
如您所见, SignupForm
组件的目录已自动创建。 那就是我们大多数代码的去向。 我还创建了一个新的User.ts
来存储我们的User模型。
HTML模板
在深入研究实际的组件模板之前,我们需要对要构建的内容有一个抽象的想法。 这就是我脑海中的表单结构。 注册表单将具有几个输入字段,一个select元素和一个checkbox元素。
这是我们将用于注册页面HTML模板。
HTML模板
<div class="row custom-row">
<div class= "col-sm-5 custom-container jumbotron">
<form class="form-horizontal">
<fieldset>
<legend>SignUp</legend>
<!--- Email Block --->
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
id="inputEmail"
placeholder="Email">
</div>
<!--- Password Block --->
<div class="form-group">
<label for="inputPassword">Password</label>
<input type="password"
id="inputPassword"
placeholder="Password">
</div>
<div class="form-group">
<label for="confirmPassword" >Confirm Password</label>
<input type="password"
id="confirmPassword"
placeholder="Password">
</div>
<!--- Select gender Block --->
<div class="form-group">
<label for="select">Gender</label>
<select id="select">
<option>Male</option>
<option>Female</option>
<option>Other</option>
</select>
</div>
<!--- Terms and conditions Block --->
<div class="form-group checkbox">
<label>
<input type="checkbox"> Confirm that you've read the Terms and
Conditions
</label>
</div>
<!--- Buttons Block --->
<div class="form-group">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</fieldset>
</form>
</div>
</div>
HTML模板中使用CSS类是用于使外观漂亮的Bootstrap库的一部分。 由于这不是设计教程,因此除非有必要,否则我不会谈论表单CSS方面。
基本表格设置
要使用模板驱动的表单指令,我们需要从@angular/forms
导入FormsModule
并将其添加到app.module.ts
的imports
数组中。
app / app.module.ts
import { FormsModule } from '@angular/forms';
@NgModule({
.
.
imports: [
BrowserModule,
FormsModule
],
.
.
})
export class AppModule { }
接下来,创建一个将包含User实体的所有属性的类。 我们可以使用接口并在组件中实现它,也可以为模型使用TypeScript类。
app / User.ts
export class User {
id: number;
email: string;
//Both the passwords are in a single object
password: {
pwd: string;
confirmPwd: string;
};
gender: string;
terms: boolean;
constructor(values: Object = {}) {
//Constructor initialization
Object.assign(this, values);
}
}
现在,在SignupForm组件中创建该类的实例。 我还宣布了性别的其他财产。
app / signup-form / signup-form.component.ts
import { Component, OnInit } from '@angular/core';
// Import the User model
import { User } from './../User';
@Component({
selector: 'app-signup-form',
templateUrl: './signup-form.component.html',
styleUrls: ['./signup-form.component.css']
})
export class SignupFormComponent implements OnInit {
//Property for the gender
private gender: string[];
//Property for the user
private user:User;
ngOnInit() {
this.gender = ['Male', 'Female', 'Others'];
//Create a new user object
this.user = new User({
email:"", password: { pwd: "" , confirm_pwd: ""},
gender: this.gender[0], terms: false});
}
}
对于signup-form.component.html文件,我将使用上面讨论的相同HTML模板,但会有一些细微变化。 注册表单具有一个带有选项列表的选择字段。 尽管这ngFor
,但我们将通过使用ngFor
指令遍历列表来以Angular方式进行ngFor
。
app / signup-form / signup-form.component.html
<div class="row custom-row">
<div class= "col-sm-5 custom-container jumbotron">
<form class="form-horizontal">
<fieldset>
<legend>SignUp</legend>
.
.
<!--- Gender Block -->
<div class="form-group">
<label for="select">Gender</label>
<select id="select">
<option *ngFor = "let g of gender"
[value] = "g"> {{g}}
</option>
</select>
</div>
.
.
</fieldset>
</form>
</div>
</div>
接下来,我们要将表单数据绑定到用户类对象,以便在将注册数据输入到表单中时,将创建一个新的User对象,该对象临时存储该数据。 这样,您可以使视图与模型保持同步,这称为绑定。
有两种方法可以实现这一目标。 首先让我向您介绍ngModel
和ngForm
。
ngForm和ngModel
ngForm和ngModel是Angular指令,对于创建模板驱动的表单至关重要。 让我们先从ngForm
开始。 这是Angular文档中有关ngForm的摘录。
NgForm
指令通过附加功能补充了form
元素。 它使用ngModel
指令和name
属性保存为元素创建的控件,并监视其属性,包括其有效性。 它还具有自己的valid
属性,仅当每个包含的控件均有效时,该属性才为true。
首先,使用ngForm
指令更新表单:
app / signup-form / signup-form.component.html
<form
class="form-horizontal"
#signupForm = "ngForm">
.
.
</form>
#signupForm
是模板引用变量 ,它引用控制整个表单的ngForm
指令。 下面的示例演示了使用ngForm
引用对象进行验证。
app / signup-form / signup-form.component.html
<button
type="submit"
class="btn btn-success"
[disabled]="!signupForm.form.valid">
Submit
</button>
在这里,除非所有表单元素均通过各自的验证检查,否则signupForm.form.valid
将返回false。 在表单有效之前,提交按钮将被禁用。
至于绑定模板和模型,有很多方法可以实现,而ngModel
具有三种不同的语法来解决这种情况。 他们是:
- [(ngModel)]
- [ngModel]
- ngModel
让我们从第一个开始。
使用[(ngModel)]进行双向绑定
[(ngModel)]
执行双向绑定以读取和写入输入控制值。 如果使用[(ngModel)]
指令,则输入字段会从绑定的组件类中获取一个初始值,并在检测到输入控制值的任何更改时(在击键和按下按钮时)将其更新。 下图更好地描述了双向绑定过程。
这是电子邮件输入字段的代码:
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
[(ngModel)] = "user.email"
id="inputEmail"
name="email"
placeholder="Email">
</div>
[(ngModel)] = "user.email"
将用户的电子邮件属性绑定到输入值。 我还添加了一个name属性,并设置了name="email"
。 这很重要,如果在使用ngModel时未声明name属性,则会出现错误。
同样,向每个表单元素添加[(ngModel)]
和唯一的name属性。 您的表单现在应如下所示:
app / signup-form / signup-form.component.html
.
.
.
<div ngModelGroup="password">
<div class="form-group" >
<label for="inputPassword">Password</label>
<input type="password"
[(ngModel)] = "user.password.pwd" name="pwd"
placeholder="Password">
</div>
<div class="form-group">
<label for="confirmPassword" >Confirm Password</label>
<input type="password"
[(ngModel)] = "user.password.confirmPwd" name="confirmPwd"
placeholder="Confirm Password">
</div>
</div>
<div class="form-group">
<label for="select">Gender</label>
<select id="select"
[(ngModel)] = "user.gender" name = "gender">
<option *ngFor = "let g of gender"
[value] = "g"> {{g}}
</option>
</select>
</div>
.
.
.
ngModelGroup
用于将相似的表单字段分组在一起,以便我们只能在那些表单字段上运行验证。 由于两个密码字段都相关,因此我们将它们放在单个ngModelGroup下。 如果一切都按预期工作,则组件绑定user
属性应负责存储所有表单控件值。 要查看实际效果,请在form标记后添加以下内容:
{{user | json}}
通过JsonPipe
传递用户属性,以在浏览器中将模型呈现为JSON。 这对于调试和记录很有帮助。 您应该看到这样的JSON输出。
值从视图流入模型。 那反过来呢? 尝试使用一些值初始化用户对象。
app / signup-form / signup-form.component.ts
this.user = new User({
//initialized with some data
email:"thisisfromthemodel@example.com",
password: { pwd: "" , confirm_pwd: ""},
gender: this.gender[0]
});
它们自动出现在视图中:
{ "email": "thisisfromthemodel@example.com",
"password": { "pwd": "", "confirm_pwd": "" },
"gender": "Male"
}
双向绑定[(ngModel)]
语法可帮助您轻松构建表单。 但是,它具有某些缺点。 因此,有另一种使用ngModel
或[ngModel]
。
将ngModel添加到混合
使用ngModel
,实际上我们负责使用输入控件值来更新组件属性,反之亦然。 输入的数据不会自动流入组件的用户属性。
因此,将[(ngModel)] = " "
所有实例替换为ngModel
。 我们将保留name
属性,因为ngModel的所有三个版本都需要name
属性才能工作。
app / signup-form / signup-form.component.html
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
ngModel
id="inputEmail"
name="email"
placeholder="Email">
</div>
使用ngModel
,name属性的值将成为我们之前创建的ngForm引用对象signupForm
的键。 因此,例如, signupForm.value.email
将存储电子邮件ID的控件值。
替换{{user | json}}
{{user | json}}
和{{signupForm.value | json }}
{{signupForm.value | json }}
因为这是现在存储所有状态的地方。
使用[ngModel]的单向绑定
如果需要从绑定的类组件设置初始状态怎么办? 那就是[ngModel]
为您做的。
在这里,数据从模型流向视图。 对语法进行以下更改以使用单向绑定:
app / signup-form / signup-form.component.html
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
[ngModel] = "user.email"
id="inputEmail"
name="email"
placeholder="Email">
</div>
那么您应该选择哪种方法呢? 如果您使用[(ngModel)]
和ngForm
在一起,你最终将有两种状态,以备存─ user
和signupForm.value
-and可能被潜在的混乱。
{ "email": "thisisfromthemodel@example.com",
"password": { "pwd": "thisispassword", "confirm_pwd": "thisispassword" },
"gender": "Male"
} //user.value
{ "email": "thisisfromthemodel@example.com",
"password": { "pwd": "thisispassword", "confirm_pwd": "thisispassword" },
"gender": "Male"
} //signupForm.value
因此,我建议改为使用单向绑定方法。 但这是您要决定的事情。
验证和显示错误消息
这是我们的验证要求。
- 所有表单控件都是必需的。
- 禁用提交按钮,直到所有输入字段均已填写。
- 电子邮件字段应严格包含电子邮件ID。
- 密码字段的最小长度为8。
- 密码和确认均应匹配。
第一个很简单。 您必须像每个表单元素一样添加required
验证属性:
app / signup-form / signup-form.component.html
<input type="text"
[ngModel] = "user.email" name="email"
#email = "ngModel"
placeholder="Email"
required>
除了required
属性外,我还导出了一个新的#email
模板参考变量。 这样,您就可以从模板本身内部访问输入框的Angular控件。 我们将使用它来显示错误和警告。 现在使用按钮的disabled属性禁用按钮:
app / signup-form / signup-form.component.html
<button
type="submit"
class="btn btn-success"
[disabled]="!signupForm.form.valid">
Submit
</button>
要对电子邮件添加约束,请使用与输入字段配合使用的pattern属性。 模式用于指定正则表达式,例如以下表达式:
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
对于密码字段,您要做的就是添加一个minlength=" "
属性:
app / signup-form / signup-form.component.html
<input type="password"
ngModel
id="inputPassword"
name="pwd"
#pwd = "ngModel"
placeholder="Password"
minlength="8"
required>
为了显示错误,我将在div元素上使用条件指令ngIf
。 让我们从电子邮件的输入控制字段开始:
app / signup-form / signup-form.component.html
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
[ngModel] = "user.email" name="email"
#email = "ngModel" id="inputEmail"
placeholder="Email"
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
required>
</div>
<!-- This is the error section -->
<div *ngIf="email.invalid && (email.dirty || email.touched)"
class="alert alert-danger">
<div *ngIf = "email.errors?.required">
Email field can't be blank
</div>
<div *ngIf = "email.errors?.pattern && email.touched">
The email id doesn't seem right
</div>
</div>
这里有很多事情。 让我们从错误部分的第一行开始。
<div *ngIf="email.invalid && (email.dirty || email.touched)"
class="alert alert-danger">
还记得我们之前导出的#email
变量吗? 它包含有关电子邮件字段的输入控制状态的一些信息。 这包括: email.valid
, email.invalid
, email.dirty
, email.pristine
, email.touched
, email.untouched
和email.errors
。 下图详细描述了每个属性。
因此,仅当电子邮件无效时,才会显示带有*ngIf
的div元素。 但是,即使在他们有机会编辑表单之前,也会给用户带来关于输入字段为空白的错误消息。
为了避免这种情况,我们添加了第二个条件。 仅在访问 了控件或更改了控件的值后,才会显示该错误。
嵌套的div元素用于覆盖验证错误的所有情况。 我们使用email.errors
来检查所有可能的验证错误,然后以自定义消息的形式将其显示给用户。 现在,对其他表单元素执行相同的步骤。 这是我对密码验证进行编码的方式。
app / signup-form / signup-form.component.html
<div ngModelGroup="password" #userPassword="ngModelGroup" required >
<div class="form-group">
<label for="inputPassword">Password</label>
<input type="password"
ngModel name="pwd"
id="inputPassword" placeholder="Password"
minlength ="8" required>
</div>
<div class="form-group">
<label for="confirmPassword" >Confirm Password</label>
<input type="password"
ngModel name="confirmPwd"
id="confirmPassword" placeholder="Confirm Password">
</div>
<div *ngIf="(userPassword.invalid|| userPassword.value?.pwd != userPassword.value?.confirmPwd) && (userPassword.touched)"
class="alert alert-danger">
<div *ngIf = "userPassword.invalid; else nomatch">
Password needs to be more than 8 characters
</div>
<ng-template #nomatch >
Passwords don't match
</ng-template>
</div>
</div>
这看起来有点混乱。 Angular具有一组有限的验证器属性: required
, minlength
, maxlength
和pattern
。 要涵盖密码比较之类的任何其他情况,您将必须像上面一样依靠嵌套的ngIf
条件。 或者理想情况下,创建一个自定义验证器函数,该函数将在本系列的第三部分中介绍。
在上面的代码中,我使用了ngIf else
最新版本中引入的ngIf else
语法。 下面是它的工作原理:
<div *ngIf="isValid;else notvalid">
Valid content...
</div>
<ng-template #notValid>Not valid content...</ng-template>
使用ngSubmit提交表单
我们几乎完成了表格。 现在我们需要能够提交表单,并且应该将表单数据的控制权移交给一个组件方法,例如onFormSubmit()
。
app / signup-form / signup-form.component.ts
<form novalidate
(ngSubmit)="onFormSubmit(signupForm)"
#signupForm="ngForm">
...
现在,对于组件:
app / signup-form / signup-form.component.ts
...
public onFormSubmit({ value, valid}: { value: User, valid: boolean }) {
this.user = value;
console.log( this.user);
console.log("valid: " + valid);
}
...
最终演示
我已经将应用程序的最终版本放在GitHub存储库中 。 您可以下载或克隆它以亲自尝试。 我添加了一些引导程序类来使表单漂亮。
摘要
我们都做完了。 在本教程中,我们涵盖了使用模板驱动的方法在Angular中创建表单所需的所有知识。 模板驱动的表单因其简单易用而广受欢迎。
但是,如果您需要构建具有许多表单元素的表单,则此方法将变得混乱。 因此,在下一个教程中,我们将介绍构建相同表单的模型驱动方式。
在下面的评论中分享您的想法。
学习JavaScript:完整指南
我们已经建立了完整的指南,可以帮助您学习JavaScript ,无论您是刚开始作为Web开发人员还是想探索更高级的主题。