前面的章节我们详细介绍了很多数据绑定,这些数据绑定的方法都是单向数据绑定。除了前面介绍的那些,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.target的value属性重新赋值给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+ 中,单向数据绑定占绝大部分。但是,这并不是说我们要避免使用双向数据绑定。只要小心地恰当使用,就可以为我们减少很多的冗余代码。