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