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 调用 ClickMeComponentonClickMe 方法。

通过 $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);
    }
  }
}

源代码

  1. 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!';
      }
    }
  2. 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; }
    }
  3. 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 { }
  4. 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 还支持被动事件侦听器。例如,你可以使用以下步骤使滚动事件变为被动监听。

  1. 在 src 目录下创建一个 "zone-flags.ts" 文件。

  2. 往这个文件中添加如下语句。
(window as any)['__zone_symbol__PASSIVE_EVENTS'] = ['scroll'];
  1. 在 "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='' 在添加新英雄到列表中后清除输入框。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号

意见反馈
返回顶部