Angular 学习之路 11 – 数据绑定(5)

前面的章节我们详细介绍了很多数据绑定,这些数据绑定的方法都是单向数据绑定。除了前面介绍的那些,Angular 还提供了双向数据绑定。

双向数据绑定

双向数据绑定意味着数据是双向流动的:既可以从组件流向视图,又可以从视图流向组件。当我们在组件中修改了数据时,修改后的数据会自动显示在视图;当用户在视图修改了数据时,修改后的数据会自动同步到组件。这一技术对于页面表单尤其有用。

不过仔细回想一下,双向数据绑定似乎并没有什么特别的东西:数据从组件流向视图,我们已经介绍过了,可以使用属性绑定;从视图流向组件,我们也介绍过了,可以使用事件绑定。如果我们把属性绑定与事件绑定结合起来,不就是双向数据绑定吗?

事实上,Angular 也是这么想的:

下面我们使用属性绑定和事件绑定,来模拟一下双向数据绑定。

@Component({
  selector: 'app-root',
  template: `
    <input type="text" [value]="name" (input)="name=$event.target?.value">
    <p>You entered {{name}}</p>
    <button (click)="clearName()">Clear</button>
  `
})
export class AppComponent {

  name = '';

  clearName(): void {
    this.name = '';
  }

}

上面的代码,我们将name绑定到<input>value属性,同时监听<input>input事件(该事件会在<input>的值发生变化时发出),将event.targetvalue属性重新赋值给name。注意这里,name既作为<input>的数据来源,在事件中又作为变化的对象,这形成了双向的数据绑定:当name变化时,value就会变化;当value变化时,name也会变化。后面的clearName()函数,则显示name的清空是如何反映到value上面的。

Angular 为上述做法引入了一个简化的语法:[()]。为方便记忆,这种语法被形象地称为 Banana in box(盒子里的香蕉),其中,[]代表属性绑定,()代表事件绑定。

<someElement [(someProperty)]="value"></someElement>

当我们有一个名为property的属性,同时有一个名为propertyChange的事件,那么,就可以使用双向绑定的语法。也就是说,如果要使用双向绑定语法,必须有一个属性以及对应的属性名+ Change 的事件。例如下面的示例:

@Component({
  selector: 'app-hello',
  template: `
    <input type="text" [value]="name" (input)="onNameChanged($event.target?.value)">
    <p>You entered {{name}}</p>
  `
})
export class HelloComponent {

  @Input() name = '';
  @Output() nameChange: EventEmitter<string> = new EventEmitter<string>();

  onNameChanged(value: string): void {
    this.nameChange.emit(value);
  }

}

这个组件有两个属性:一个输入属性,使用@Input()修饰,一个输出属性,也就是事件,使用@Output()修饰。输入属性名字是 name,因此输出属性名字必须是 nameChange。这样才能使用[(name)]作为双向数据绑定。

在另外的组件中使用<app-hello>

<app-hello [(name)]="myName"></app-hello>

双向数据绑定是一种简单但功能强大的机制,其本质就是将属性绑定与事件绑定结合起来。然而,双向数据绑定也是一把双刃剑,功能强大的同时,也可以轻松扰乱数据流,让你不知道数据在哪里发生了变化。这也就是为什么,Angular 1.x 中,所有绑定都是双向数据绑定,然而到了 Angular 2+ 中,单向数据绑定占绝大部分。但是,这并不是说我们要避免使用双向数据绑定。只要小心地恰当使用,就可以为我们减少很多的冗余代码。

Leave a Reply