Angular9 用户输入
用户输入
当用户点击链接、按下按钮或者输入文字时,这些用户动作都会产生 DOM 事件。 本章解释如何使用 Angular 事件绑定语法把这些事件绑定到事件处理器。
绑定到用户输入事件
你可以使用 Angular 事件绑定机制来响应任何 DOM 事件。 许多 DOM 事件是由用户输入触发的。绑定这些事件可以获取用户输入。
要绑定 DOM 事件,只要把 DOM 事件的名字包裹在圆括号中,然后用放在引号中的模板语句对它赋值就可以了。
下例展示了一个事件绑定,它实现了一个点击事件处理器:
Path:"src/app/click-me.component.ts"
<button (click)="onClickMe()">Click me!</button>
等号左边的 (click)
表示把按钮的点击事件作为绑定目标。 等号右边引号中的文本是模板语句,通过调用组件的 onClickMe
方法来响应这个点击事件。
写绑定时,需要知道模板语句的执行上下文。 出现在模板语句中的每个标识符都属于特定的上下文对象。 这个对象通常都是控制此模板的 Angular 组件。 上例中只显示了一行 HTML,那段 HTML 片段属于下面这个组件:
Path:"src/app/click-me.component.ts"
@Component({
selector: 'app-click-me',
template: `
<button (click)="onClickMe()">Click me!</button>
{{clickMessage}}`
})
export class ClickMeComponent {
clickMessage = '';
onClickMe() {
this.clickMessage = 'You are my hero!';
}
}
当用户点击按钮时,Angular 调用 ClickMeComponent
的 onClickMe
方法。
通过 $event 对象取得用户输入
DOM 事件可以携带可能对组件有用的信息。 本节将展示如何绑定输入框的 keyup
事件,在每个敲击键盘时获取用户输入。
下面的代码监听 keyup
事件,并将整个事件载荷 ($event)
传给组件的事件处理器。
Path:"src/app/keyup.components.ts (template v.1)"
template: `
<input (keyup)="onKey($event)">
<p>{{values}}</p>
`
当用户按下并释放一个按键时,触发 keyup
事件,Angular 在 $event
变量提供一个相应的 DOM 事件对象,上面的代码将它作为参数传给 onKey()
方法。
Path:"src/app/keyup.components.ts (class v.1)"
export class KeyUpComponent_v1 {
values = '';
onKey(event: any) { // without type info
this.values += event.target.value + ' | ';
}
}
$event
对象的属性取决于 DOM 事件的类型。例如,鼠标事件与输入框编辑事件包含了不同的信息。
所有标准 DOM 事件对象都有一个 target
属性, 引用触发该事件的元素。 在本例中,target 是 <input>
元素, event.target.value
返回该元素的当前内容。
在组件的 onKey()
方法中,把输入框的值和分隔符 (|
) 追加组件的 values
属性。 使用插值来把存放累加结果的 values
属性回显到屏幕上。
假设用户输入字母“abc”,然后用退格键一个一个删除它们。 用户界面将显示:
a | ab | abc | ab | a | |
或者,你可以用
event.key
替代event.target.value
,积累各个按键本身,这样同样的用户输入可以产生:
&
a | b | c | backspace | backspace | backspace |
$event的类型
上例将 $event
转换为 any
类型。 这样简化了代码,但是有成本。 没有任何类型信息能够揭示事件对象的属性,防止简单的错误。
下面的例子,使用了带类型方法:
Path:"src/app/keyup.components.ts (class v.1 - typed )"
export class KeyUpComponent_v1 {
values = '';
onKey(event: KeyboardEvent) { // with type info
this.values += (event.target as HTMLInputElement).value + ' | ';
}
}
$event
的类型现在是 KeyboardEvent
。 不是所有的元素都有 value
属性,所以它将 target
转换为输入元素。 OnKey
方法更加清晰地表达了它期望从模板得到什么,以及它是如何解析事件的。
传入 $event 是靠不住的做法
类型化事件对象揭露了重要的一点,即反对把整个 DOM 事件传到方法中,因为这样组件会知道太多模板的信息。 只有当它知道更多它本不应了解的 HTML 实现细节时,它才能提取信息。 这就违反了模板(用户看到的)和组件(应用如何处理用户数据)之间的分离关注原则。
下面将介绍如何用模板引用变量来解决这个问题。
从一个模板引用变量中获得用户输入
还有另一种获取用户数据的方式:使用 Angular 的模板引用变量。 这些变量提供了从模块中直接访问元素的能力。 在标识符前加上井号 (#
) 就能声明一个模板引用变量。
下面的例子使用了局部模板变量,在一个超简单的模板中实现按键反馈功能。
Path:"src/app/loop-back.component.ts"
@Component({
selector: 'app-loop-back',
template: `
<input #box (keyup)="0">
<p>{{box.value}}</p>
`
})
export class LoopbackComponent { }
这个模板引用变量名叫 box
,在 <input>
元素声明,它引用 <input>
元素本身。 代码使用 box
获得输入元素的 value
值,并通过插值把它显示在 <p>
标签中。
这个模板完全是完全自包含的。它没有绑定到组件,组件也没做任何事情。
在输入框中输入,就会看到每次按键时,显示也随之更新了。
除非你绑定一个事件,否则这将完全无法工作。
只有在应用做了些异步事件(如击键),Angular 才更新绑定(并最终影响到屏幕)。 本例代码将
keyup
事件绑定到了数字 0,这可能是最短的模板语句了。 虽然这个语句不做什么,但它满足 Angular 的要求,所以 Angular 将更新屏幕。
从模板变量获得输入框比通过 $event
对象更加简单。 下面的代码重写了之前 keyup
示例,它使用变量来获得用户输入。
Path:"src/app/keyup.components.ts (v2)"
@Component({
selector: 'app-key-up2',
template: `
<input #box (keyup)="onKey(box.value)">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v2 {
values = '';
onKey(value: string) {
this.values += value + ' | ';
}
}
这个方法最漂亮的一点是:组件代码从视图中获得了干净的数据值。再也不用了解 $event
变量及其结构了。
按键事件过滤(通过 key.enter)
(keyup)
事件处理器监听每一次按键。 有时只在意回车键,因为它标志着用户结束输入。 解决这个问题的一种方法是检查每个 $event.keyCode
,只有键值是回车键时才采取行动。
更简单的方法是:绑定到 Angular 的 keyup.enter
模拟事件。 然后,只有当用户敲回车键时,Angular 才会调用事件处理器。
Path:"src/app/keyup.components.ts (v3)"
@Component({
selector: 'app-key-up3',
template: `
<input #box (keyup.enter)="onEnter(box.value)">
<p>{{value}}</p>
`
})
export class KeyUpComponent_v3 {
value = '';
onEnter(value: string) { this.value = value; }
}
下面展示了它的工作原理。
失去焦点事件 (blur)
前上例中,如果用户没有先按回车键,而是移开了鼠标,点击了页面中其它地方,输入框的当前值就会丢失。 只有当用户按下了回车键候,组件的 values
属性才能更新。
下面通过同时监听输入框的回车键和失去焦点事件来修正这个问题。
Path:"src/app/keyup.components.ts (v4)"
@Component({
selector: 'app-key-up4',
template: `
<input #box
(keyup.enter)="update(box.value)"
(blur)="update(box.value)">
<p>{{value}}</p>
`
})
export class KeyUpComponent_v4 {
value = '';
update(value: string) { this.value = value; }
}
结合使用
现在,在一个微型应用中一起使用它们,应用能显示一个英雄列表,并把新的英雄加到列表中。 用户可以通过输入英雄名和点击“添加”按钮来添加英雄。
下面就是“简版英雄指南”组件。
Path:"src/app/little-tour.component.ts"
@Component({
selector: 'app-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>
`
})
export class LittleTourComponent {
heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
addHero(newHero: string) {
if (newHero) {
this.heroes.push(newHero);
}
}
}
源代码
- Path:"src/app/click-me.component.ts"
import { Component } from '@angular/core';
@Component({
selector: 'app-click-me',
template: `
<button (click)="onClickMe()">Click me!</button>
{{clickMessage}}`
})
export class ClickMeComponent {
clickMessage = '';
onClickMe() {
this.clickMessage = 'You are my hero!';
}
}
- Path:"src/app/keyup.components.ts"
import { Component } from '@angular/core';
@Component({
selector: 'app-key-up1',
template: `
<input (keyup)="onKey($event)">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v1 {
values = '';
/*
onKey(event: any) { // without type info
this.values += event.target.value + ' | ';
}
*/
onKey(event: KeyboardEvent) { // with type info
this.values += (event.target as HTMLInputElement).value + ' | ';
}
}
//////////////////////////////////////////
@Component({
selector: 'app-key-up2',
template: `
<input #box (keyup)="onKey(box.value)">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v2 {
values = '';
onKey(value: string) {
this.values += value + ' | ';
}
}
//////////////////////////////////////////
@Component({
selector: 'app-key-up3',
template: `
<input #box (keyup.enter)="onEnter(box.value)">
<p>{{value}}</p>
`
})
export class KeyUpComponent_v3 {
value = '';
onEnter(value: string) { this.value = value; }
}
//////////////////////////////////////////
@Component({
selector: 'app-key-up4',
template: `
<input #box
(keyup.enter)="update(box.value)"
(blur)="update(box.value)">
<p>{{value}}</p>
`
})
export class KeyUpComponent_v4 {
value = '';
update(value: string) { this.value = value; }
}
- Path:"src/app/loop-back.component.ts"
import { Component } from '@angular/core';
@Component({
selector: 'app-loop-back',
template: `
<input #box (keyup)="0">
<p>{{box.value}}</p>
`
})
export class LoopbackComponent { }
- Path:"src/app/little-tour.component.ts"
import { Component } from '@angular/core';
@Component({
selector: 'app-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>
`
})
export class LittleTourComponent {
heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
addHero(newHero: string) {
if (newHero) {
this.heroes.push(newHero);
}
}
}
Angular 还支持被动事件侦听器。例如,你可以使用以下步骤使滚动事件变为被动监听。
- 在 src 目录下创建一个 "zone-flags.ts" 文件。
- 往这个文件中添加如下语句。
(window as any)['__zone_symbol__PASSIVE_EVENTS'] = ['scroll'];
- 在 "src/polyfills.ts" 文件中,导入 "zone.js" 之前,先导入新创建的 "zone-flags" 文件。
import './zone-flags';
import 'zone.js/dist/zone'; // Included with Angular CLI.
经过这些步骤,你添加 scroll
事件的监听器时,它就是被动(passive)
的。
小结
- 使用模板变量来引用元素 —
newHero
模板变量引用了<input>
元素。 你可以在<input>
的任何兄弟或子级元素中引用newHero
。
- 传递数值,而非元素 — 获取输入框的值并将它传给组件的
addHero
,而不要传递newHero
。
- 保持模板语句简单 —
(blur)
事件被绑定到两个 JavaScript 语句。 第一句调用addHero
。第二句newHero.value=''
在添加新英雄到列表中后清除输入框。
更多建议: