您将要创建 的响应式表单:
表单对于任何现代前端应用程序都至关重要,它们是我们每天都在使用的功能,即使没有意识到。需要表单才能安全地让用户登录到应用程序、搜索特定城市中所有可用的酒店、预订出租车、建立待办事项清单以及做很多我们习惯做的其他事情。一些表单只有几个输入字段,而其他表单可能有一个字段数组,可以延伸到几个页面或选项卡。
在本教程中,我们将讨论可用于在 angular 中开发表单的不同策略。无论您选择何种策略,以下是表单库应涵盖的内容:
支持双向绑定,使输入控件值与组件状态同步。
跟踪表单状态并使用视觉提示让用户知道当前状态是否有效。例如,如果用户名包含无效字符,则用户名的输入字段周围应出现红色边框。
具有正确显示验证错误的机制。
除非满足某些验证条件,否则启用或禁用表单的某些部分。
Angular 中的表单简介
Angular作为一个成熟的前端框架,有自己的一套用于构建复杂表单的库。最新版本的 Angular 有两个强大的表单构建策略。他们是:
模板驱动的表单
模型驱动或反应形式
这两种技术都属于该 @angular/forms
库,并且基于相同的表单控件类。但是,它们在哲学、编程风格和技术方面存在显着差异。选择其中一个取决于您的个人品味以及您尝试创建的表单的复杂性。在我看来,您应该先尝试这两种方法,然后选择适合您的风格和手头项目的方法。
本教程的第一部分将通过一个实际示例介绍模板驱动的表单:构建一个对所有表单字段进行验证的注册表单。在本教程的第二部分,我们将追溯使用模型驱动方法创建相同表单的步骤。
模板驱动的表单
模板驱动的方法是从 AngularJS 时代借来的一种策略。在我看来,这是构建表单最直接的方法。它是如何工作的?我们将使用一些 Angular 指令。
指令允许您将行为附加到dom中的元素。
— 角度文档
Angular 提供了特定于表单的指令,您可以使用这些指令来绑定表单输入数据和模型。特定于表单的指令为纯 html 表单添加了额外的功能和行为。最终结果是模板负责绑定值与模型和表单验证。
在本教程中,我们将使用模板驱动的表单来创建应用程序的注册页面。该表单将涵盖最常见的表单元素以及对这些表单元素的不同验证检查。以下是您将在本教程中遵循的步骤。
将 FormsModule 添加到
app.module.ts
.为 User 模型创建一个类。
为注册表单创建初始组件和布局。
使用 Angular 表单指令,如
ngModel
、ngModelGroup
和ngForm
.使用内置验证器添加验证。
有意义地显示验证错误。
使用 处理表单提交
ngSubmit
。
让我们开始吧。
先决条件
该项目的代码可在我的 GitHub存储库中找到 。下载 zip 或克隆 repo 以查看它的实际效果。如果您更喜欢从头开始,请确保您已安装 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
的用于存储我们的用户模型。
HTML 模板
在我们深入了解实际的组件模板之前,我们需要对我们正在构建的内容有一个抽象的概念。所以这是我脑海中的表单结构。注册表单将有几个输入字段、一个选择元素和一个复选框元素。
这是我们将用于注册页面的 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 方面。
基本表单设置
要使用模板驱动的表单指令,我们需要导入 FormsModule
from @angular/forms
并将其添加到 in 的imports
数组中app.module.ts
。
应用程序/app.module.ts
import { FormsModule } from '@angular/forms'; @NgModule({ . . imports: [ BrowserModule, FormsModule ], . . }) export class AppModule { }
接下来,创建一个包含 User 实体的所有属性的类。我们可以使用接口并在组件中实现它,也可以为模型使用typescript类。
应用程序/用户.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
指令循环列表来以 Angular 的方式进行。
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>
接下来,我们要将表单数据绑定到用户类对象,这样当您将注册数据输入表单时,会创建一个新的用户对象来临时存储该数据。这样,您可以使视图与模型保持同步,这称为绑定。
有几种方法可以实现这一点。让我首先向您介绍ngModel
和ngForm
。
ngForm 和 ngModel
ngForm 和 ngModel 是创建模板驱动表单必不可少的 Angular 指令。让我们ngForm
先开始吧。这是 Angular 文档中关于 ngForm 的摘录。
该NgForm
指令form
用附加功能补充元素。它包含您为具有ngModel
指令和name
属性的元素创建的控件,并监视它们的属性,包括它们的有效性。它也有自己的valid
属性,只有 当每个包含的控件 都有效时才为真。
首先,使用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)]
[ng模型]
模型
让我们从第一个开始。
使用 [(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)]
唯一名称属性。您的表单现在应该如下所示:
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
属性应该负责存储所有表单控件值。要查看此操作,请在表单标记后添加以下内容:
{{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
or [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 属性的值将成为signupForm
我们之前创建的 ngForm 引用对象的键。因此,例如,signupForm.value.email
将存储电子邮件 ID 的控制值。
替换 {{user | 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)]
and ngForm
,你最终将有两个状态需要维护——而且——这可能会造成混淆。 user
signupForm.value
{ "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="[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>
为了显示错误,我将ngIf
在 div 元素上使用条件指令。让我们从电子邮件的输入控制字段开始:
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
了最新版本的 Angular 中引入的语法。下面是它的工作原理:
<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 中创建表单所需了解的所有内容。模板驱动的表单因其简单易用而广受欢迎。
但是,如果您需要构建一个包含大量表单元素的表单,这种方法将变得混乱。因此,在下一个教程中,我们将介绍构建相同表单的模型驱动方式。
- HTML 模板
- HTML 模板
- 应用程序/app.module.ts
- 应用程序/用户.ts
- app/signup-form/signup-form.component.ts
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.ts
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.html
- app/signup-form/signup-form.component.ts
- app/signup-form/signup-form.component.ts