当用户点击链接、或按下某个按钮、或录入文字时,会触发DOM事件。本章节讲述如何在Angular里处理这些绑定事件。

一、绑定用户录入事件

Angular绑定事件对象与DOM事件完成一致,只不过需要使用一对括号括弧起来,例如点击事件:

<button (click)="onClickMe()">Click me!</button>

等号右边的内容是模板表达式(意味者可以包括:组件类方法、表达式),只不过需要注意上下文,因为他非常的特殊,通常模板表达式上下文是组件类,但是你无法直接在表达式里使用 this 来表达组件类实例对象。

@Component({
  selector: 'click-me',
  template: `
    <button (click)="onClickMe()">Click me!</button>
    {{clickMessage}}`
})
export class ClickMeComponent {
  clickMessage = '';

  onClickMe(){
    this.clickMessage ='You are my hero!';
  }
}

注意 onClickMe() 假如我们加上 this 会直接报错。(其实我自己很郁闷到底 this 的意义在这里算不算特殊)。(click) 右边内容为模板表达式,意味者我们可以这么用:(click)="clickMessage='test';"

二、从$event对象获取用户录入

先看一下 keyup 示例:

@Component({
  selector: 'my-app',
  template: `
      <input (keyup)="onKey($event)">
      <p>{{values}}</p>`
})
export class AppComponent {
    values = '';

    onKey(event: any) {
        this.values += event.target.value + ' | ';
    }
}

Angular会先创建一个 $event 变量,并在事件触发时传递给 onKey() 方法。

$event 类型由事件类型所决定,keyup 事件来自DOM,所以 $event 一定是标准的DOM事件对象,$event.target 返回的就是 HTMLInputElement,自然 value 就是用户录入值。

上面示例代码的 $event 是一个 any 类型,意味着放弃强大TypeScript强类型来简化代码,强类型版本应该是:

export class KeyUpComponent_v1 {
  values='';

  // with strong typing
  onKey(event:KeyboardEvent) {
    this.values += (<HTMLInputElement>event.target).value + ' | ';
  }
}

但是强类型又暴露一个严重问题,组件类必须知道更多模板细节,反而违反了关注点分离这一原则。

三、从本地模板变量获取用户录入

很明显使用事件变量就会有上面提到的纠结问题,故而用另一种使用本地模板变量来获取用户录入。

Angular有个叫本地模板变量的模板语法特征,这种变量可以直接访问到元素自身,其声明方式只需要在变量名加上 #

以下是一个非常简单利用本地模板变量记录用户录入的示例:

@Component({
  selector: 'my-app',
  template:`
    <input #box (keyup)="0">
    <p>{{box.value}}</p>
  `
})
export class LoopbackComponent { }

<input> 声明一个 #box 本地模板变量,box 就是 <input> 元素的自身引用,因此可以在模板的当前元素、兄弟元素、和任意子元素中直接使用。

上面完全没有使用到任何组件类的东西,其中 (keyup)="0" 已经算是能想到最简化的代码。

同时,另一个问题接踵而至,如何把本地模板变量传给组件类呢?既然 box 表示input元素自身引用,我们可以在模板里把input的值以传参形式传递给组件类。

@Component({
  selector: 'my-app',
  template: `
      <input #box (keyup)="onKey(box.value)">
      <p>{{values}}</p>`
})
export class AppComponent {
    values = '';
    onKey(value: string) {
        this.values += value + ' | ';
    }
}

这样组件类简洁获得到录入信息,同时又不需要告诉事件和DOM结构信息给组件类。

四、按钮事件过滤(使用KEY.ENTER

有时候我只需要关心部分按钮,比如回车,绑定 (keyup) 所有按键都会被触发。一种可以检查 $event.keyCode是否为回车符。而Angular提供另一种更便利来过滤指定某个按键的方法,比如 keyup.enter 表示当按键为回车才会触发事件。以下示例是当按回车后才会将值传递给 values

@Component({
  selector: 'my-app',
  template: `
      <input #box (keyup.enter)="values=box.value">
      <p>{{values}}</p>`
})
export class AppComponent {
    values = '';
}

注:enter 实则是keyCode相应的英文符号,几乎包括键盘所有按键(比如:按下A时才触发 (keyup.a)),具体见源代码

五、blur事件

上面示例并没有考虑焦点问题,如果没有按回车就离开焦点值也应更新。下面添加 blur 事情后的示例:

@Component({
  selector: 'my-app',
  template: `
      <input #box 
        (keyup.enter)="values=box.value"
        (blur)="values=box.value">
      <p>{{values}}</p>`
})
export class AppComponent {
    values = '';
}

六、一个完整的示例

用户在文本框里录入信息后,可以点击添加按钮、或回车键、或点击页面的其他位置(即失去input焦点)给列表添加新项。

@Component({
  selector: 'my-app',
  template: `
      <input #newHero 
        (keyup.enter)="addHero(newHero.value)"
        (blur)="addHero(newHero.value); newHero.value=''">
      <button (click)=addHero(newHero.value)>Add</button>

      <ul>
        <li *ngFor="#hero of heroes">{{hero}}</li>
      </ul>
    `
})
export class AppComponent {
    heroes:string[] = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
    addHero(newHero: string) {
        if (newHero) this.heroes.push(newHero);
    }
}

借示例我们来总结一下:

使用本地模板变量引用元素自身

newHero 模板变量引用 <input> 元素,我们可以在模板任何位置使用该变量。从模板变量中获取元素可以简化事件处理;而不需要使用CSS选择器来查找元素。

值传递,非元素传递

一开始 addHero 组件类方法需要依赖于DOM结构信息,这样组件类非常笨拙,而且一但模板变更组件也要一起变动。相反,使用值传递,组件类就变得更DOM无关,这也是我们喜欢看到的。

保持模板简洁

示例为了避免将DOM元素传递给组件类。首先必须使用 addHero 来处理文本框录入,其次还需要依赖两个JavaScript事件(keyup和blur)来维护被合理触发。除此之外还有另一种办法 ngModel 双向绑定,来解决这一问题。见下一篇表单章节。