AngularDart4.0 指南- 表单
2017-12-06 00:00
429 查看
摘要: AngularDart4.0 指南- 表单
表单是商业应用程序的主流。您可以使用表单登录,提交帮助请求,下订单,预订航班,安排会议,并执行无数其他数据录入任务。
在开发表单时,创建一个数据录入体验非常重要,该体验可以通过工作流高效地引导用户。
开发表单需要设计技巧(超出本页面的范围),以及双向数据绑定,更改跟踪,验证和错误处理的框架支持,您将在本页面上了解这些信息。
本页面向您展示了如何从头构建一个简单的表单。一路上你将学习如何:
用组件和模板构建一个Angular表单。
使用ngModel创建读取和写入输入控制值的双向数据绑定。
跟踪状态变化和表单控件的有效性。
使用跟踪控件状态的特殊CSS类提供视觉反馈。
向用户显示验证错误并启用/禁用表单控件。
使用模板引用变量在HTML元素之间共享信息。
您可以在Plunker中运行实例(查看源代码)并从那里下载代码。
您也可以使用响应式(或模型驱动)方法来构建表单。 但是,此页面重点介绍模板驱动的表单。
您可以使用Angular模板 构建几乎任何表单- 登录表单,联系表单和几乎任何业务表单。 您可以创造性地设计控件,将它们绑定到数据,指定验证规则和显示验证错误,有条件地启用或禁用特定控件,触发内置的视觉反馈等等。
Angular通过许多重复的,模板化的任务使处理过程变得简单。
您将学习如何构建一个模板驱动的表单,如下所示:
英雄就业机构使用这种形式来维护关于英雄的个人信息。 每个英雄都需要一份工作。 让正确的英雄与正确的危机相匹配是公司的使命。
这个表格中的三个字段中的两个是必需的。 遵循材料设计准则,必填字段带有星号(*)。
如果您删除了英雄名称,表单将以吸引人注意的风格显示验证错误:
请注意提交按钮被禁用,并且输入控件从绿色变为红色。
您将以小步骤构建此表单:
创建英雄模型类。
创建控制表单的组件。
用初始表单布局创建一个模板。
使用ngModel双向数据绑定语法将数据属性绑定到每个表单控件。
为每个表单输入控件添加一个ngControl指令。
添加自定义CSS来提供视觉反馈。
显示和隐藏验证错误消息。
使用ngSubmit处理表单提交。
禁用窗体的提交按钮,直到窗体有效。
一个模型可以像“钱包”一样简单,掌握关于应用程序重要事实的事实。 这很好地描述了英雄类与三个必填字段(id, name, power)和一个可选字段(alterEgo)。
在lib目录中,使用给定的内容创建以下文件:lib/src/hero.dart
这是一个缺乏要求,没有行为的鸡肋模型,对于演示来说足够了。
alterEgo是可选的,所以构造函数可以让你忽略它。 请注意[this.alterEgo]中的括号。
你可以像这样创建一个新的英雄:
这个组件没有什么特别之处,没有任何特定的形式,没有什么区别它与你之前写的任何组件。
理解这个组件只需要前面几页中介绍的Angular概念。
代码导入您刚创建的主Angular库和Hero模型。
hero-form的@Component选择器值意味着您可以使用<hero-form>元素将此表单放在父模板中。
templateUrl属性指向模板HTML的单独文件(您将很快创建)。
您为model和power定义了模拟数据。
顺便说一句,您可以注入数据服务来获取和保存真实数据,或者将这些属性作为输入和输出(请参阅“模板语法”页面中的输入和输出属性)来绑定到父组件。 这不是现在的问题,这些未来的变化不会影响表单。
将初学者应用版本的内容替换为以下内容:lib/app_component.dart
该语言只是HTML5。 您将展示两个Hero字段,name和alterEgo,并在输入框中将其打开以供用户输入。
Name <input>控件具有HTML5必需属性; Alter Ego <input>控件什么也不做,因为alterEgo是可选的。
您在底部添加了一个提交按钮,其中有一些类用于样式。
你还没有使用Angular。 没有绑定或额外的指令,只是布局。
在模板驱动的表单中,如果已经导入了angular_forms库,则不必为了使用库功能而对<form>标记执行任何操作。 继续看看这是如何工作的。
刷新浏览器。 你会看到一个简单的,没有样式的表单。
Angular可不使用Bootstrap类或任何外部库的样式。 Angular的应用程序可以使用任何CSS库或不使用。
通过将以下链接插入到index.html的<head>中来添加Bootstrap样式:web/index.html (bootstrap)
刷新浏览器。 你会看到一个样式化的表单!
您将在表单中添加一个select,并使用ngFor(先前在“显示数据”页面中看到的一种技术)将选项绑定到powers列表。
在Alter Ego group下方添加以下HTML:lib/src/hero_form_component.html (powers)
这段代码重复列表中每个power 的<option>标签。 p模板输入变量在每次迭代中是不同的power; 您使用插值语法显示其名称。
你没有看到英雄数据,因为你还没有绑定到英雄。 你知道如何从早期的页面做到这一点。 显示数据教导属性绑定。 用户输入显示如何使用事件绑定监听DOM事件以及如何使用显示的值更新组件属性。
现在您需要同时显示,聆听和提取。
你可以使用你已经知道的技术,但是你会使用新的[(ngModel)]语法,这使得绑定到模型的表单变得容易。
找到Name的<input>标签,并像下面这样更新它:lib/src/hero_form_component.html (name)
你在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)
每个input元素都有一个id属性,label元素的for属性使用它来匹配label和input控件。
每个input元素都有一个ngControl指令,Angular表单需要用这个指令在表单上注册控件。
如果您现在运行应用程序并更改每个英雄model属性,表单可能会显示如下:
靠近表单顶部的诊断确认所有的更改都反映在model中。
从模板中删除诊断绑定,因为它已经达到了目的。
每个Angular控制(NgControl)都跟踪自己的状态,并通过以下字段成员使状态可供检查:
dirty和pristine表明控制的值是否已经改变。
touched和untouched指示控件是否被访问过。
valid反映了控制值的有效性。
将名为name的模板引用变量添加到Name <input>标记中。 使用name和类绑定来有条件地分配适当的表单有效性类。
临时将另一个名为spy的模板引用变量添加到Name <input>标记,并使用它显示输入的CSS类。
lib/src/hero_form_component.html (name)
为什么“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)
使用此方法返回的映射值绑定到NgClass指令 - 在模板语法页面中详细了解此指令及其替代方法。
lib/src/hero_form_component.html (power)
为了达到这个效果,在Name <input>之后立即添加下面的<div>:
lib/src/hero_form_component.html (hidden error message)
刷新浏览器并删除Name 输入。 显示错误消息。
您可以通过根据名称控制的状态设置<div>的隐藏属性来控制错误消息的可见性。
在这个例子中,当控件是有效的或者原始的时候隐藏消息 - “pristine”意味着用户没有改变这个值,因为它是以这种形式显示的。
有些开发人员希望仅在用户进行无效更改时显示消息。 当控件是“原始的”时隐藏消息实现了这个目标。 当您向表单添加一个“清除”按钮时,您会看到此选项的重要性。
英雄Alter Ego是可选的,所以你可以不用关那个。
英雄power选择是必需的。 如果需要,可以将相同类型的错误消息添加到<select>中,但这不是必须的,因为选择框已经将权限限制为有效值。
在提交按钮后面添加一个带有点击事件绑定的清除按钮:lib/src/hero_form_component.html (Clear button)
刷新浏览器。 点击清除按钮。 文本字段变为空白,如果您更改了power,它将恢复为默认值。
表单提交目前是无用的。 为了使它有用,将表单组件的onSubmit()方法分配给表单的ngSubmit事件绑定:
请注意模板引用变量#heroForm。 正如前面所解释的,变量heroForm被绑定到整体管理表单的NgForm指令。
NgForm指令补充表单元素的附加功能。 它包含用ngModel和ngControl指令为元素创建的控件,并监视它们的属性,包括它们的有效性。
您将通过heroForm变量将表单的整体有效性绑定到按钮的disabled属性:
刷新浏览器。 你会发现这个按钮是启用的,尽管它没有做任何有用的事情。
现在,如果您删除Name,则违反了“必需的”规则,这在错误消息中正确记录。 提交按钮也被禁用。
没有留下深刻印象? 想一想。 如果没有Angular的帮助,你需要做什么才能将按钮的启用/禁用状态连接到表单的有效性?
对你来说,这很简单:
在(增强的)表单元素上定义一个模板引用变量。
在多处的按钮中引用该变量。
如预期的演示。 增加代码过后的demo不会教你任何关于表单的新东西。 但是这是一个锻炼一些新获得的绑定技巧的机会。 如果您不感兴趣,请跳至本页的摘要。
作为一种视觉效果,您可以隐藏数据输入区域并显示其他内容。
将表单封装在<div>中,并将其hidden属性绑定到HeroFormComponent.submitted属性。
lib/src/hero_form_component.html (excerpt)
该表单从一开始就是可见的,因为在提交表单之前,提交的属性为false,因为HeroFormComponent中的片段显示为:lib/src/hero_form_component.dart (submitted)
现在在刚刚写的<div>包装器下面添加下面的HTML:lib/src/hero_form_component.html (submitted)
刷新浏览器并提交表单。 提交的标志变为真,表格消失。 您将看到表格中显示的英雄模型值(只读)。
该视图包含一个编辑按钮,其单击事件绑定将清除提交的标志。 当您单击编辑按钮时,该表消失,并且可编辑的表单重新出现。
一个HTML表单模板和一个带有@Component注解的表单组件类。
表单提交,通过ngSubmit事件绑定处理。
模板引用变量,如heroForm和name。
双向数据绑定([(ngModel)])。
用于验证和表单元素更改跟踪的NgControl 指令。
输入控件(通过模板引用变量访问)的valid 属性,用于检查控件有效性以及显示/隐藏错误消息。
NgForm.form的有效性来设置提交按钮的启用状态。
自定义CSS类为用户提供有关控制状态的可视反馈。
最终的项目文件夹结构应该如下所示:
以下是应用程序最终版本的代码:
lib/app_component.dart
lib/src/hero.dart
lib/src/hero_form_component.dart
lib/src/hero_form_component.html
web/index.html
web/main.dart
表单是商业应用程序的主流。您可以使用表单登录,提交帮助请求,下订单,预订航班,安排会议,并执行无数其他数据录入任务。
在开发表单时,创建一个数据录入体验非常重要,该体验可以通过工作流高效地引导用户。
开发表单需要设计技巧(超出本页面的范围),以及双向数据绑定,更改跟踪,验证和错误处理的框架支持,您将在本页面上了解这些信息。
本页面向您展示了如何从头构建一个简单的表单。一路上你将学习如何:
用组件和模板构建一个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); }
相关文章推荐
- AngularDart4.0 指南- 用户输入
- AngularDart4.0 指南- 模板语法二
- AngularDart4.0 指南-体系结构概述
- AngularDart4.0 指南- 显示数据
- AngularDart4.0 指南- 依赖注入
- AngularDart4.0 指南
- AngularDart4.0 指南- 模板语法一
- AngularDart4.0 英雄之旅-教程-05多组件
- AngularDart4.0 英雄之旅-教程-04明细
- AngularDart4.0 英雄之旅-教程-07路由
- AngularDart 4.0 高级-HTTP 客户端
- AngularDart4.0 高级-层级依赖注入器
- AngularDart4.0 英雄之旅-教程-06服务
- AngularDart4.0 高级-部署
- AngularDart4.0 英雄之旅-教程-08HTTP
- AngularDart4.0 英雄之旅-教程-02启动应用
- AngularDart4.0 高级-属性(Attribute)指令
- AngularDart4.0 高级-组件样式
- AngularDart4.0 英雄之旅-教程-01介绍
- AngularDart4.0 英雄之旅-教程-03英雄编辑器