您的位置:首页 > Web前端 > AngularJS

AngularDart4.0 指南- 表单

2017-12-06 00:00 429 查看
摘要: AngularDart4.0 指南- 表单

表单是商业应用程序的主流。您可以使用表单登录,提交帮助请求,下订单,预订航班,安排会议,并执行无数其他数据录入任务。

在开发表单时,创建一个数据录入体验非常重要,该体验可以通过工作流高效地引导用户。

开发表单需要设计技巧(超出本页面的范围),以及双向数据绑定,更改跟踪,验证和错误处理的框架支持,您将在本页面上了解这些信息。

本页面向您展示了如何从头构建一个简单的表单。一路上你将学习如何:

用组件和模板构建一个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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  AngularDart Angular Dart