2019独角兽企业重金招聘Python工程师标准>>>
表单是商业应用程序的主流。您可以使用表单登录,提交帮助请求,下订单,预订航班,安排会议,并执行无数其他数据录入任务。
在开发表单时,创建一个数据录入体验非常重要,该体验可以通过工作流高效地引导用户。
开发表单需要设计技巧(超出本页面的范围),以及双向数据绑定,更改跟踪,验证和错误处理的框架支持,您将在本页面上了解这些信息。
本页面向您展示了如何从头构建一个简单的表单。一路上你将学习如何:
- 用组件和模板构建一个Angular表单。
- 使用ngModel创建读取和写入输入控制值的双向数据绑定。
- 跟踪状态变化和表单控件的有效性。
- 使用跟踪控件状态的特殊CSS类提供视觉反馈。
- 向用户显示验证错误并启用/禁用表单控件。
- 使用模板引用变量在HTML元素之间共享信息。
您可以在Plunker中运行实例(查看源代码)并从那里下载代码。
模板驱动的形式
您可以通过使用本页中描述的特定于表单的指令和技术在Angular模板语法中编写模板来构建表单。
您也可以使用响应式(或模型驱动)方法来构建表单。 但是,此页面重点介绍模板驱动的表单。
您可以使用Angular模板 构建几乎任何表单- 登录表单,联系表单和几乎任何业务表单。 您可以创造性地设计控件,将它们绑定到数据,指定验证规则和显示验证错误,有条件地启用或禁用特定控件,触发内置的视觉反馈等等。
Angular通过许多重复的,模板化的任务使处理过程变得简单。
您将学习如何构建一个模板驱动的表单,如下所示:
英雄就业机构使用这种形式来维护关于英雄的个人信息。 每个英雄都需要一份工作。 让正确的英雄与正确的危机相匹配是公司的使命。
这个表格中的三个字段中的两个是必需的。 遵循材料设计准则,必填字段带有星号(*)。
如果您删除了英雄名称,表单将以吸引人注意的风格显示验证错误:
请注意提交按钮被禁用,并且输入控件从绿色变为红色。
您将以小步骤构建此表单:
- 创建英雄模型类。
- 创建控制表单的组件。
- 用初始表单布局创建一个模板。
- 使用ngModel双向数据绑定语法将数据属性绑定到每个表单控件。
- 为每个表单输入控件添加一个ngControl指令。
- 添加自定义CSS来提供视觉反馈。
- 显示和隐藏验证错误消息。
- 使用ngSubmit处理表单提交。
- 禁用窗体的提交按钮,直到窗体有效。
建立
按照设置说明创建一个名为表单的新项目。
添加angular_forms
Angular表单功能位于angular_forms库中,该库位于其自己的包中。 将该包添加到pubspec依赖项:
创建一个模型
当用户输入表单数据时,您将捕获其更改并更新模型的实例。 直到你知道模型是什么样子,你才能布置表格。
一个模型可以像“钱包”一样简单,掌握关于应用程序重要事实的事实。 这很好地描述了英雄类与三个必填字段(id, name, power)和一个可选字段(alterEgo)。
在lib目录中,使用给定的内容创建以下文件:lib/src/hero.dart
class Hero {int id;String name, power, alterEgo;Hero(this.id, this.name, this.power, [this.alterEgo]);String toString() => '$id: $name ($alterEgo). Super power: $power';
}
这是一个缺乏要求,没有行为的鸡肋模型,对于演示来说足够了。
alterEgo是可选的,所以构造函数可以让你忽略它。 请注意[this.alterEgo]中的括号。
你可以像这样创建一个新的英雄:
var myHero = new Hero(42, 'SkyDog', 'Fetch any object at any distance', 'Leslie Rollover');
print('My hero is ${myHero.name}.'); // "My hero is SkyDog."
创建一个基本的表单
一个Angular表单有两个部分:一个基于HTML的模板和一个组件类,以编程方式处理数据和用户交互。 从课程开始,因为它简要地说明了英雄编辑可以做什么。
创建一个表单组件
使用给定的内容创建以下文件:lib/src/hero_form_component.dart (v1)
import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';import 'hero.dart';const List<String> _powers = const ['Really Smart','Super Flexible','Super Hot','Weather Changer'
];@Component(selector: 'hero-form',templateUrl: 'hero_form_component.html',directives: const [CORE_DIRECTIVES, formDirectives],
)
class HeroFormComponent {Hero model = new Hero(18, 'Dr IQ', _powers[0], 'Chuck Overstreet');bool submitted = false;List<String> get powers => _powers;void onSubmit() => submitted = true;
}
这个组件没有什么特别之处,没有任何特定的形式,没有什么区别它与你之前写的任何组件。
理解这个组件只需要前面几页中介绍的Angular概念。
- 代码导入您刚创建的主Angular库和Hero模型。
- hero-form的@Component选择器值意味着您可以使用<hero-form>元素将此表单放在父模板中。
- templateUrl属性指向模板HTML的单独文件(您将很快创建)。
- 您为model和power定义了模拟数据。
顺便说一句,您可以注入数据服务来获取和保存真实数据,或者将这些属性作为输入和输出(请参阅“模板语法”页面中的输入和输出属性)来绑定到父组件。 这不是现在的问题,这些未来的变化不会影响表单。
修改应用程序组件
AppComponent是应用程序的根组件。 它将承载HeroFormComponent。
将初学者应用版本的内容替换为以下内容:lib/app_component.dart
import 'package:angular/angular.dart';import 'src/hero_form_component.dart';@Component(selector: 'my-app',template: '<hero-form></hero-form>',directives: const [HeroFormComponent],
)
class AppComponent {}
创建一个初始表单模板
使用以下内容创建模板文件:lib/src/hero_form_component.html (start)
<div class="container"><h1>Hero Form</h1><form><div class="form-group"><label for="name">Name *</label><input type="text" class="form-control" id="name" required></div><div class="form-group"><label for="alterEgo">Alter Ego</label><input type="text" class="form-control" id="alterEgo"></div><div class="row"><div class="col-auto"><button type="submit" class="btn btn-primary">Submit</button></div><small class="col text-right">* Required</small></div></form>
</div>
该语言只是HTML5。 您将展示两个Hero字段,name和alterEgo,并在输入框中将其打开以供用户输入。
Name <input>控件具有HTML5必需属性; Alter Ego <input>控件什么也不做,因为alterEgo是可选的。
您在底部添加了一个提交按钮,其中有一些类用于样式。
你还没有使用Angular。 没有绑定或额外的指令,只是布局。
在模板驱动的表单中,如果已经导入了angular_forms库,则不必为了使用库功能而对<form>标记执行任何操作。 继续看看这是如何工作的。
刷新浏览器。 你会看到一个简单的,没有样式的表单。
表单的样式
一般的CSS类container和btn来自Bootstrap。 Bootstrap还具有form-specific的类,包括form-control和form-group。 一起,这些给表单了一些样式。
Angular可不使用Bootstrap类或任何外部库的样式。 Angular的应用程序可以使用任何CSS库或不使用。
通过将以下链接插入到index.html的<head>中来添加Bootstrap样式:web/index.html (bootstrap)
<link rel="stylesheet"href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"crossorigin="anonymous">
刷新浏览器。 你会看到一个样式化的表单!
使用* ngFor添加powers
英雄必须从一个固定的机构批准的权力列表中选择一个超级大国。 您在内部维护该列表(在HeroFormComponent中)。
您将在表单中添加一个select,并使用ngFor(先前在“显示数据”页面中看到的一种技术)将选项绑定到powers列表。
在Alter Ego group下方添加以下HTML:lib/src/hero_form_component.html (powers)
<div class="form-group"><label for="power">Hero Power *</label><select class="form-control" id="power" required><option *ngFor="let p of powers" [value]="p">{{p}}</option></select>
</div>
这段代码重复列表中每个power 的<option>标签。 p模板输入变量在每次迭代中是不同的power; 您使用插值语法显示其名称。
与ngModel的双向数据绑定
现在运行应用程序有点令人失望。
你没有看到英雄数据,因为你还没有绑定到英雄。 你知道如何从早期的页面做到这一点。 显示数据教导属性绑定。 用户输入显示如何使用事件绑定监听DOM事件以及如何使用显示的值更新组件属性。
现在您需要同时显示,聆听和提取。
你可以使用你已经知道的技术,但是你会使用新的[(ngModel)]语法,这使得绑定到模型的表单变得容易。
找到Name的<input>标签,并像下面这样更新它:lib/src/hero_form_component.html (name)
<!-- TODO: remove the next diagnostic line -->
<mark>{{model.name}}</mark><hr>
<div class="form-group"><label for="name">Name *</label><input type="text" class="form-control" id="name" required[(ngModel)]="model.name"ngControl="name">
</div>
你在form-group之前添加了一个诊断插值,所以你可以看到你在做什么。 当你完成的时候,你留下一张纸条扔掉它。
关注绑定语法:[(ngModel)] =“...”。
现在运行应用程序并输入名称输入,添加和删除字符。 您会看到这些字符出现在诊断文本中并消失。 在某个时候,它可能看起来像这样:
诊断结果表明数值确实是从输入流向模型,再返回。
这是双向的数据绑定。 有关更多信息,请参见模板语法页面上的与NgModel的双向绑定。
请注意,您还为<input>标记添加了一个ngControl指令,并将其设置为“name”,这对于英雄的名字是有意义的。 任何唯一值将会这样做,但使用描述性名称是有帮助的。 将[(ngModel)]与表单结合使用时,定义ngControl指令是一项要求。
在内部,Angular创建NgFormControl实例,并使用Angular附加到<form>标签的NgForm指令注册它们。 每个NgFormControl都是在您分配给ngControl指令的名称下注册的。 本指南稍后将详细介绍NgForm。
在Alter Ego和Hero Power上添加类似的[(ngModel)]绑定和ngControl指令。
用model替换诊断绑定表达式。 通过这种方式,您可以确认双向数据绑定适用于整个英雄模型。
修改后,表单的核心应该是这样的:lib/src/hero_form_component.html (controls)
<!-- TODO: remove the next diagnostic line -->
<mark>{{model}}</mark><hr>
<div class="form-group"><label for="name">Name *</label><input type="text" class="form-control" id="name" required[(ngModel)]="model.name"ngControl="name">
</div>
<div class="form-group"><label for="alterEgo">Alter Ego</label><input type="text" class="form-control" id="alterEgo"[(ngModel)]="model.alterEgo"ngControl="alterEgo">
</div>
<div class="form-group"><label for="power">Hero Power *</label><select class="form-control" id="power" required[(ngModel)]="model.power"ngControl="power"><option *ngFor="let p of powers" [value]="p">{{p}}</option></select>
</div>
- 每个input元素都有一个id属性,label元素的for属性使用它来匹配label和input控件。
- 每个input元素都有一个ngControl指令,Angular表单需要用这个指令在表单上注册控件。
如果您现在运行应用程序并更改每个英雄model属性,表单可能会显示如下:
靠近表单顶部的诊断确认所有的更改都反映在model中。
从模板中删除诊断绑定,因为它已经达到了目的。
根据控制状态给出视觉反馈
使用CSS和类绑定,您可以更改表单控件的外观以反映其状态。
跟踪控制状态
Angular表单控件可以告诉您用户是否触摸了该控件,值是否改变,或者该值是否失效。
每个Angular控制(NgControl)都跟踪自己的状态,并通过以下字段成员使状态可供检查:
- dirty和pristine表明控制的值是否已经改变。
- touched和untouched指示控件是否被访问过。
- valid反映了控制值的有效性。
样式控件
有效的控制属性是最有趣的,因为当一个控制值无效时,你想发送一个强烈的视觉信号。 要创建这样的视觉反馈,您将使用Bootstrap自定义表单类 is-valid和is-invalid。
将名为name的模板引用变量添加到Name <input>标记中。 使用name和类绑定来有条件地分配适当的表单有效性类。
临时将另一个名为spy的模板引用变量添加到Name <input>标记,并使用它显示输入的CSS类。
lib/src/hero_form_component.html (name)
<input type="text" class="form-control" id="name" required[(ngModel)]="model.name"#name="ngForm"#spy[class.is-valid]="name.valid"[class.is-invalid]="!name.valid"ngControl="name">
<!-- TODO: remove the next diagnostic line -->
{{spy.className}}
模板引用变量
spy模板引用变量绑定到<input> DOM元素,而name变量(通过#name =“ngForm”语法)绑定到与input元素关联的NgModel。
为什么“ngForm”? 指令的exportAs属性告诉Angular如何将引用变量链接到指令。 您将name设置为“ngForm”,因为ngModel指令的exportAs属性是“ngForm”。
刷新浏览器,然后按照下列步骤操作:
1.看看名字输入。
- 它有一个绿色的边框。
- 它具有类形式控制和有效性。
2.通过添加一些字符来更改name。 类保持不变。
3.删除名称。
- 输入框边框变为红色。
- is-invalid类替换为is-valid。
删除#spy模板引用变量和使用它的诊断。
作为类绑定的替代方法,可以使用NgClass指令来设置控件的样式。 首先,添加以下方法来设置控件的依赖于状态的CSS类名称:
lib/src/hero_form_component.dart (setCssValidityClass)
Map<String, bool> setCssValidityClass(NgControl control) {final validityClass = control.valid == true ? 'is-valid' : 'is-invalid';return {validityClass: true};
}
使用此方法返回的映射值绑定到NgClass指令 - 在模板语法页面中详细了解此指令及其替代方法。
lib/src/hero_form_component.html (power)
<select class="form-control" id="power" required[(ngModel)]="model.power"#power="ngForm"[ngClass]="setCssValidityClass(power)"ngControl="power"><option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
显示并隐藏验证错误消息
你可以改善表格。 名称输入是必需的,清除它将框的轮廓变为红色。 这说明有些事情是错的,但用户不知道什么是错的,或者该怎么做。 利用控件的状态来显示有用的消息。
使用有效的和原始的状态
当用户删除名称时,表单应该如下所示:
为了达到这个效果,在Name <input>之后立即添加下面的<div>:
lib/src/hero_form_component.html (hidden error message)
<div [hidden]="name.valid || name.pristine" class="invalid-feedback">Name is required
</div>
刷新浏览器并删除Name 输入。 显示错误消息。
您可以通过根据名称控制的状态设置<div>的隐藏属性来控制错误消息的可见性。
在这个例子中,当控件是有效的或者原始的时候隐藏消息 - “pristine”意味着用户没有改变这个值,因为它是以这种形式显示的。
用户体验是开发者的选择
有些开发人员希望消息始终显示。 如果您忽略原始状态,则只有在该值有效时才会隐藏该消息。 如果您使用新(空白)英雄或无效英雄到达此组件,则在您执行任何操作之前,您将立即看到错误消息。
有些开发人员希望仅在用户进行无效更改时显示消息。 当控件是“原始的”时隐藏消息实现了这个目标。 当您向表单添加一个“清除”按钮时,您会看到此选项的重要性。
英雄Alter Ego是可选的,所以你可以不用关那个。
英雄power选择是必需的。 如果需要,可以将相同类型的错误消息添加到<select>中,但这不是必须的,因为选择框已经将权限限制为有效值。
添加一个清除按钮
将clear()方法添加到组件类中:lib/src/hero_form_component.dart (clear)
void clear() {model.name = '';model.power = _powers[0];model.alterEgo = '';
}
在提交按钮后面添加一个带有点击事件绑定的清除按钮:lib/src/hero_form_component.html (Clear button)
<button (click)="clear()" type="button" class="btn">Clear
</button>
刷新浏览器。 点击清除按钮。 文本字段变为空白,如果您更改了power,它将恢复为默认值。
用ngSubmit提交表单
用户应该能够在填写表单后提交这个表单。表单底部的Submit按钮本身不做任何事情,但是由于它的类型(type =“submit”),它会触发一个表单提交。
表单提交目前是无用的。 为了使它有用,将表单组件的onSubmit()方法分配给表单的ngSubmit事件绑定:
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
请注意模板引用变量#heroForm。 正如前面所解释的,变量heroForm被绑定到整体管理表单的NgForm指令。
NgForm指令
Angular自动创建并附加一个NgForm指令给<form>标签。
NgForm指令补充表单元素的附加功能。 它包含用ngModel和ngControl指令为元素创建的控件,并监视它们的属性,包括它们的有效性。
您将通过heroForm变量将表单的整体有效性绑定到按钮的disabled属性:
<button [disabled]="!heroForm.form.valid" type="submit" class="btn btn-primary">Submit
</button>
刷新浏览器。 你会发现这个按钮是启用的,尽管它没有做任何有用的事情。
现在,如果您删除Name,则违反了“必需的”规则,这在错误消息中正确记录。 提交按钮也被禁用。
没有留下深刻印象? 想一想。 如果没有Angular的帮助,你需要做什么才能将按钮的启用/禁用状态连接到表单的有效性?
对你来说,这很简单:
- 在(增强的)表单元素上定义一个模板引用变量。
- 在多处的按钮中引用该变量。
显示Model(可选)
提交表单目前没有视觉效果。
如预期的演示。 增加代码过后的demo不会教你任何关于表单的新东西。 但是这是一个锻炼一些新获得的绑定技巧的机会。 如果您不感兴趣,请跳至本页的摘要。
作为一种视觉效果,您可以隐藏数据输入区域并显示其他内容。
将表单封装在<div>中,并将其hidden属性绑定到HeroFormComponent.submitted属性。
lib/src/hero_form_component.html (excerpt)
<div [hidden]="submitted"><h1>Hero Form</h1><form (ngSubmit)="onSubmit()" #heroForm="ngForm"></form>
</div>
该表单从一开始就是可见的,因为在提交表单之前,提交的属性为false,因为HeroFormComponent中的片段显示为:lib/src/hero_form_component.dart (submitted)
bool submitted = false;void onSubmit() => submitted = true;
现在在刚刚写的<div>包装器下面添加下面的HTML:lib/src/hero_form_component.html (submitted)
<div [hidden]="!submitted"><h1>Hero data</h1><table class="table"><tr><th>Name</th><td>{{model.name}}</td></tr><tr><th>Alter Ego</th><td>{{model.alterEgo}}</td></tr><tr><th>Power</th><td>{{model.power}}</td></tr></table><button (click)="submitted=false" class="btn btn-primary">Edit</button>
</div>
刷新浏览器并提交表单。 提交的标志变为真,表格消失。 您将看到表格中显示的英雄模型值(只读)。
该视图包含一个编辑按钮,其单击事件绑定将清除提交的标志。 当您单击编辑按钮时,该表消失,并且可编辑的表单重新出现。
概要
Angular表单为数据修改,验证等提供支持。 在此页面中,您学习了如何使用以下功能:
- 一个HTML表单模板和一个带有@Component注解的表单组件类。
- 表单提交,通过ngSubmit事件绑定处理。
- 模板引用变量,如heroForm和name。
- 双向数据绑定([(ngModel)])。
- 用于验证和表单元素更改跟踪的NgControl 指令。
- 输入控件(通过模板引用变量访问)的valid 属性,用于检查控件有效性以及显示/隐藏错误消息。
- NgForm.form的有效性来设置提交按钮的启用状态。
- 自定义CSS类为用户提供有关控制状态的可视反馈。
最终的项目文件夹结构应该如下所示:
以下是应用程序最终版本的代码:
lib/app_component.dart
import 'package:angular/angular.dart';
import 'src/hero_form_component.dart';
@Component(selector: 'my-app',template: '<hero-form></hero-form>',directives: const [HeroFormComponent],
)
class AppComponent {}
lib/src/hero.dart
class Hero {int id;String name, power, alterEgo;Hero(this.id, this.name, this.power, [this.alterEgo]);String toString() => '$id: $name ($alterEgo). Super power: $power';
}
lib/src/hero_form_component.dart
import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';
import 'hero.dart';
const List<String> _powers = const ['Really Smart','Super Flexible','Super Hot','Weather Changer'
];
@Component(selector: 'hero-form',templateUrl: 'hero_form_component.html',directives: const [CORE_DIRECTIVES, formDirectives],
)
class HeroFormComponent {Hero model = new Hero(18, 'Dr IQ', _powers[0], 'Chuck Overstreet');bool submitted = false;List<String> get powers => _powers;void onSubmit() => submitted = true;/// Returns a map of CSS class names representing the state of [control].Map<String, bool> setCssValidityClass(NgControl control) {final validityClass = control.valid == true ? 'is-valid' : 'is-invalid';return {validityClass: true};}void clear() {model.name = '';model.power = _powers[0];model.alterEgo = '';}
}
lib/src/hero_form_component.html
<div class="container"><div [hidden]="submitted"><h1>Hero Form</h1><form (ngSubmit)="onSubmit()" #heroForm="ngForm"><div class="form-group"><label for="name">Name *</label><input type="text" class="form-control" id="name" required[(ngModel)]="model.name"#name="ngForm"[class.is-valid]="name.valid"[class.is-invalid]="!name.valid"ngControl="name"><div [hidden]="name.valid || name.pristine" class="invalid-feedback">Name is required</div></div><div class="form-group"><label for="alterEgo">Alter Ego</label><input type="text" class="form-control" id="alterEgo"[(ngModel)]="model.alterEgo"ngControl="alterEgo"></div><div class="form-group"><label for="power">Hero Power *</label><select class="form-control" id="power" required[(ngModel)]="model.power"#power="ngForm"[ngClass]="setCssValidityClass(power)"ngControl="power"><option *ngFor="let p of powers" [value]="p">{{p}}</option></select></div><div class="row"><div class="col-auto"><button [disabled]="!heroForm.form.valid" type="submit" class="btn btn-primary">Submit</button><button (click)="clear()" type="button" class="btn">Clear</button></div><small class="col text-right">* Required</small></div></form></div><div [hidden]="!submitted"><h1>Hero data</h1><table class="table"><tr><th>Name</th><td>{{model.name}}</td></tr><tr><th>Alter Ego</th><td>{{model.alterEgo}}</td></tr><tr><th>Power</th><td>{{model.power}}</td></tr></table><button (click)="submitted=false" class="btn btn-primary">Edit</button></div>
</div>
web/index.html
<!DOCTYPE html>
<html><head><script>// WARNING: DO NOT set the <base href> like this in production!// Details: https://webdev.dartlang.org/angular/guide/router(function () {var m = document.location.pathname.match(/^(\/[-\w]+)+\/web($|\/)/);document.write('<base href="' + (m ? m[0] : '/') + '" />');}());</script><title>Hero Form</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet"href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css"integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"crossorigin="anonymous"><link rel="stylesheet" href="styles.css"><link rel="icon" type="image/png" href="favicon.png"><script defer src="main.dart" type="application/dart"></script><script defer src="packages/browser/dart.js"></script></head><body><my-app>Loading ...</my-app></body>
</html>
web/main.dart
import 'package:angular/angular.dart';
import 'package:forms/app_component.dart';
void main() {bootstrap(AppComponent);
}