Angular 学习之路 10 – 数据绑定(4)

前面我们已经使用三个章节介绍了从组件到视图的单向数据绑定,本章我们将介绍从视图到组件的单项数据绑定。

从视图到组件

从视图到组件的数据改变,通常是由用户直接或间接触发的,比如用户的键盘输入、鼠标点击等动作,因此这类数据绑定实际就是事件绑定。

事件绑定

Angular 事件绑定使用如下语法:

(target-event)="TemplateStatement"

target-event使用()包围,是事件的名字;TemplateStatement是模板语句。当target-event发生时,TempalteStatement会被自动执行。例如:

@Component({
  selector: 'app-my',
  template: `
    <button (click)="onButtonClicked()">Button</button>
  `,
  styles: [
  ]
})
export class MyComponent {
  onButtonClicked() {
    // ...
  }
}

上面代码中,我们将<button>click事件绑定到组件类的onButtonClicked()函数。当用户点击按钮,发出click事件时,onButtonClicked()会自动调用。

回忆一下,之前我们在介绍插值或者属性绑定的时候,使用的是“模板表达式 template expression”,而这里使用的是“模板语句 template statement”。前面我们说过,模板表达式不能具有副作用,不能改变应用的状态;但模板语句是可以的。这意味着,我们使用事件绑定的时候,可以对组件状态做修改。当事件绑定发生时,Angular 会自动执行数据的变更检测,按照新的数据更新视图,从而保证数据的保存和显示是一致的。

我们可以将多个事件处理器绑定到一个事件。其写法类似普通的 JavaScript 代码,例如:

@Component({
  selector: 'app-my',
  template: `
    <button (click)="onButtonClicked(); clickCount++">Button</button>
  `,
  styles: [
  ]
})
export class MyComponent {
  clickCount = 0;
  onButtonClicked() {
    // ...
  }
}

$event

事件可能有参数,这个参数使用$event传递给模板语句。例如:

@Component({
  selector: 'app-my',
  template: `
    <button (click)="onButtonClicked($event)">Button</button>
  `,
  styles: [
  ]
})
export class MyComponent {
  clickCount = 0;
  onButtonClicked(event: MouseEvent) {
    // ...
  }
}

注意这里的$event仅在模板中使用,其它地方是没有定义的。所以,我们在 HTML 中使用的是$event,而在对应的组件 TypeScript 中使用的则是普通的event作为参数名(当然,形参名字写作$event也没有关系,但这与模板中的$event是完全不一样的)。上面的代码中,由于click事件是 DOM 事件,所以$event类型是MouseEvent。如果是我们自定义的事件,则$event类型就是我们自定义事件的参数类型。这一点我们会在后面章节详细介绍。

但是,对于 DOM 事件,简单地使用$event并不是一个好的做法,因为这会将组件和 DOM 事件绑定在一起,导致组件知道了过多的模板信息,并且难以进行测试。为解决这一问题,我们可以使用模板引用变量。

模板引用变量

前面我们说过,模板引用变量代表了一个模板中的元素。例如:

@Component({
  selector: 'app-my',
  template: `
    <input #input />
    <button (click)="onButtonClicked(input)">Button</button>
  `,
  styles: [
  ]
})
export class MyComponent {
  value: string;
  onButtonClicked(input: HTMLInputElement) {
    this.value = input.value;
  }
}

使用模板引用变量,事件处理函数的参数类型就是模板引用变量指向的元素的类型。直接使用模板引用变量与之前提到的直接使用$event没有本质的区别,问题都是组件和 DOM 耦合在一起。所以,更好地做法是:

@Component({
  selector: 'app-my',
  template: `
    <input #input />
    <button (click)="onButtonClicked(input.value)">Button</button>
  `,
  styles: [
  ]
})
export class MyComponent {
  value: string;
  onButtonClicked(value: string) {
    this.value = value;
  }
}

这样,我们就避免了将整个元素作为参数,而仅使用了其输入的值。当然,我们也可以同时传递多个参数,例如:

@Component({
  selector: 'app-my',
  template: `
    <input #input />
    <button (click)="onButtonClicked(input, $event, 'btn')">Button</button>
  `,
  styles: [
  ]
})
export class MyComponent {
  value: string;
  onButtonClicked(input: HTMLInputElement, event: MouseEvent, id: string) {
    this.value = input.value;
  }
}

键盘事件过滤

我们使用keydownkeyup事件监听键盘事件,例如下面的代码:

<input (keyup)="onKeyUp($event)">

但是很多时候,我们只关心特定键盘按键的事件,例如,我们只去处理回车事件。当然,我们可以在keyup事件绑定的回调函数中利用if语句进行判断。Angular 为我们提供了更方便的方法:键盘事件过滤。我们只需使用特殊的语法就可以达到这一目的:

<input (keyup.enter)="onEnterKeyUp($event)" (keyup.escape)="onEscKeyUp($event)">

上面的代码,我们只监听回车和 ESC 键的事件,其余按键则不关心。Angular 将这种语法称为“伪事件”。伪事件功能强大,我们甚至可以监听组合键:

<input (keyup.control.shift.enter)="whatever()">

自定义事件

前面我们使用的都是 DOM 事件。事实上,Angular 提供了自定义事件,允许我们发出自己的事件。自定义事件使用EventEmitter类实现。例如下面的示例:

@Component({
  selector: 'app-my',
  template: `
    <button (click)="onButtonClicked()">Button</button>
  `
})
export class MyComponent {
  @Output() buttonClicked: EventEmitter<void> = new EventEmitter<void>();
  onButtonClicked() {
    this.buttonClicked.emit();
  }
}

我们定义了一个app-my组件,使用@Output()修饰符定义了一个事件buttonClicked,其类型是EventEmitter。当用户点击了组件中的按钮时,这个buttonClicked事件就会发出(通过emit()函数)。那么,在使用了app-my的组件中,就可以像使用 DOM 事件一样使用这个自定义事件:

<app-my (buttonClicked)="youClickThisButton()"></app-my>

我们会在以后的章节中详细介绍自定义事件。

Leave a Reply