用户输入
用户的动作如:点击链接、按下按钮或者输入文字,都会产生 DOM 事件。本章解释如何使用 Angular 事件绑定语法把这些事件绑定到事件处理器。
绑定到用户输入事件
我们可以使用 Angular 事件绑定机制来响应任意 DOM 事件。许多 DOM 事件是由用户输入触发的。绑定这些事件提供了一种获取用户输入的方式。
要绑定 DOM 事件,只要把 DOM 事件的名字包裹在圆括号中,然后用放在引号中的模板语句对它赋值就可以了。下面的例子展示了一个事件绑定,它实现了一个点击事件处理器:
<button (click)="onClickMe()">Click me!</button>
等号左边的(click)
表示把按钮的点击事件作为绑定目标。等号右边引号中的文本是模板语句,通过调用组件的onClickMe()
方法来响应这个点击事件。
写绑定时,必须要知道模板语句的执行上下文。出现在模板语句中的每个标识符都属于特定的上下文对象。这个对象通常都是控制此模板的 Angular 组件。上面的例子中只显示了一行 HTML,那段 HTML 片段属于一个更大的组件:
// lib/src/click_me_component.dart (component)
@Component(
selector: 'click-me',
template: '''
<button (click)="onClickMe()">Click me!</button>
{{clickMessage}}
''',
)
class ClickMeComponent {
String clickMessage = '';
void onClickMe() => clickMessage = 'You are my hero!';
}
当用户点击按钮时,Angular 调用ClickMeComponent
的onClickMe()
方法。
从 $event 对象获取用户输入
DOM 事件可以携带可能对组件有用的信息。这一部分将展示如何绑定输入框的 keyup
事件,在每个敲击键盘时获取用户输入。
下面的代码监听 keyup
事件,并将整个事件载荷 ($event)
传递给组件的事件处理器。
// lib/src/keyup_components.dart (v1 template)
template: '''
<input (keyup)="onKey(\$event)"> // 注意前面的 \,因为这是在dart文件中,如果是html则不需要
<p>{{values}}</p>
'''
模板在 Dart 文件中,需要在
$
前面加\
。如果模板是外部的 HTML 文件,则直接使用$event
。
当用户按下并释放一个按键时,触发keyup
事件,Angular 在$event
变量提供一个相应的 DOM 事件对象,上面的代码将它作为参数传递给onKey()
方法。
class KeyUp1Component {
String values = '';
void onKey(dynamic event) {
values += event.target.value + ' | ';
}
}
$event
对象的属性取决于 DOM 事件的类型。例如,鼠标事件和输入框编辑事件包含不同的信息。
所有标准 DOM 事件对象都有一个target
属性,引用触发该事件的元素。在本例中,target
是 <input> 元素,event.target.value
返回该元素的当前内容。
每次调用后,onKey()
方法把输入框的值和后面跟着的分隔符 (|
) 添加到组件的values
属性。模板使用 Angular 的插值表达式{{...}}
来显示values
属性。
假设用户输入字母“abc”,然后用退格键一个一个删除它们。 用户界面将显示:
a | ab | abc | ab | a | |
或者,你可以用
event.key
替代event.target.value
,在这个例中,同样的用户输入产生结果如下:
a | b | c | Backspace | Backspace | Backspace |
event 的类型
上面的例子声明的onKey() event
参数是dynamic
。尽管这样简化了代码,但使用更具体的类型可以揭露实事件对象的属性并防止愚蠢的错误。
下面的例子,使用类型重写了方法:
// lib/src/keyup_components.dart (v1 class)
class KeyUp1Component {
String values = '';
void onKey(KeyboardEvent event) {
InputElement el = event.target;
values += '${el.value} | ';
}
}
现在event
声明为KeyboardEvent
类型,event.target
声明为一个拥有 value
属性的InputElement
类型。使用这些类型,onKey()
方法更加清晰的表达了它期望从模板得到什么,以及它是如何解析事件的。
传入 $event 是不可靠的做法
类型化事件对象通过传递整个 DOM 事件到方法中,暴露了一个重要问题:组件和模板细节紧密的联系起来。组件不使用 web APIs,它就不能够提取信息。这就违反了模板(用户看到的)和组件(应用如何处理用户数据)之间关注点分离的原则。
下面的部分将介绍如何用模板引用变量来解决这个问题。
从一个模板引用变量中获得用户输入
还有另一种方法获取用户数据:Angular 模板引用变量提供了从模块中直接访问元素的能力。在标识符前加上井号 (#) 就能声明一个模板引用变量。
下面的例子使用了局部模板变量,在一个超简单的模板中实现按键反馈功能。
@Component(
selector: 'loop-back',
template: '''
<input #box (keyup)="0">
<p>{{box.value}}</p>
''',
)
class LoopBackComponent {}
在 <input>
元素上声明的名为box
的模板引用变量,引用<input>
元素本身。代码使用box
变量来获得输入元素的value
值,并通过插值表达式把它显示在<p>
标签中。
这个模板是完全自包含的。它没有绑定到组件,组件也没做任何事情。
在输入框中输入,就会看到每次按键时,显示也随之更新了。
除非你绑定一个事件,否则这将完全无法工作。
只有在应用做了些异步事件(如按键),Angular 才更新绑定(并最终影响到屏幕)。本例代码将keyup
事件绑定到了数字 0,这可能是最短的模板语句了。虽然这个语句不做什么,但它满足 Angular 的要求,所以 Angular 将更新屏幕。
从模板引用变量获得输入框比通过 $event 对象更加简单。下面的代码使用模板引用变量来获得用户输入,重写了之前keyup
示例。
lib/src/keyup_components.dart (v2)
@Component(
selector: 'key-up2',
template: '''
<input #box (keyup)="onKey(box.value)">
<p>{{values}}</p>
''',
)
class KeyUp2Component {
String values = '';
void onKey(value) => values += '$value | ';
}
这个方法最漂亮的一点是:组件代码从视图中获得了干净的数据值。它不再需要$event
的信息及其结构了。
按键事件过滤(使用key.enter
)
(keyup)
事件处理器监听每一次按键。有时只在意回车键,因为它标志着用户结束输入。解决这个问题的一种方法是检查每个$event.keyCode
,只有键值是回车键时才采取行动。
更简单的方法是:绑定到 Angular 的 keyup.enter
模拟事件。然后,只有当用户敲回车键时,Angular 才会调用事件处理器。
// lib/src/keyup_components.dart (v3)
@Component(
selector: 'key-up3',
template: '''
<input #box (keyup.enter)="values=box.value">
<p>{{values}}</p>
''',
)
class KeyUp3Component {
String values = '';
}
下面展示了它是如何工作的。
失去焦点事件
在前面的例中,如果用户没有先按回车键,而是移开了鼠标,点击了页面中其它地方,输入框的当前值就会丢失。只有当用户按下了回车键后,组件的values
属性才能更新。
让我们通过同时监听输入框的回车键和失去焦点事件来修复这个问题。
// lib/src/keyup_components.dart (v4)
@Component(
selector: 'key-up4',
template: '''
<input #box
(keyup.enter)="values=box.value"
(blur)="values=box.value">
<p>{{values}}</p>
''',
)
class KeyUp4Component {
String values = '';
}
把它们放在一起
上一章介绍了如何显示数据。本章展示了事件绑定方法。
现在,在一个微型应用中一起使用它们,应用能显示一个英雄列表,并把新的英雄加到列表中。用户可以通过输入英雄名和点击添加按钮来添加英雄。
下面就是“Little Tour of Heroes”组件。
// lib/src/little_tour_component.dart (little-tour)
@Component(
selector: 'little-tour',
template: '''
<input #newHero
(keyup.enter)="addHero(newHero.value)"
(blur)="addHero(newHero.value); newHero.value='' ">
<button (click)="addHero(newHero.value)">Add</button>
<ul><li *ngFor="let hero of heroes">{{hero}}</li></ul>
''',
directives: [coreDirectives],
)
class LittleTourComponent {
List<String> heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
void addHero(String newHero) {
if (newHero == null || newHero.isEmpty) return;
heroes.add(newHero);
}
}
注意
-
使用模板变量来引用元素。
newHero
模板变量引用了<input>
元素。你可以在<input>
的任何兄弟或子级元素中引用newHero
。 -
传递数值,而非元素。获取输入框的值并将它传递给组件的
addHero()
方法,而不是传递newHero
。 -
保持模板语句简单。
(blur)
事件绑定了两个语句。第一句调用addHero()
方法。第二句newHero.value=''
在添加新英雄到列表中后清空输入框。
总结
你已经看过了响应用户输入和操作的基础原理。
这些方法对小规模演示很实用,但是在处理大量用户输入时,很容易变得累赘和笨拙。要在数据录入字段和模型属性之间传递数据,双向数据绑定是更加优雅和简洁的方式。下一章Forms
解释了如何用NgModel
来进行双向绑定。
下一步