[Angular 基础] - 数据绑定(databinding)
上篇笔记,关于 Angular 的渲染过程及组件的创建&简单学习:[Angular 基础] - Angular 渲染过程 & 组件的创建
Angular 之中的 databinding 是一个相对而言更加复杂,以及我个人觉得相对而言比较灵活的部分------较之 React 的单项数据流而言,Angular 是可以实现双重绑定的:

对于 React 来说,则是需要调用从 ViewModel 中传给 View 层的事件,随后 ViewModel 更新数据,再传递到 View 层,总体上来说 React 的代码更加的可靠(因为数据/事件的流动是单一的),但是也会碰到情况------如嵌套较深时,事件的触发与数据的更新就会产生比较麻烦的情况
这也是二者对于事件和数据处理的不同之处
本篇笔记会对 Angular 的数据绑定进行更加深入地学习
数据传输
即 ViewModel 层将数据传输给 View 层,这里主要学习两种方式:字符串插值(string interpolation) 和 属性绑定(property binding)
string interpolation
string interpolation 是一种比较方便的将数据从 ViewModel 传到 View 层的方法,只需要在中组件中声明对应的变量/方法,并且在 HTML Template 中调用即可。用法如下:
-
在组件中声明变量/方法
tsexport class ServerComponent { serverId = 10; serverStatus = 'offline'; getServerStatus() { return this.serverStatus; } }
-
使用
{``{ var/method() }}
的方式调用html<p>{{ "Server" }} with ID {{ serverId }} is {{ getServerStatus() }}</p>
⚠️:
var/method()
为一个表达式
效果为:

缺点在于:
-
返回值必须是字符串
如果是 primitive type 那么问题不大,数字、布尔值都是可以直接转成字符串,因此正常渲染
如果是对象的话,则会调用默认的
toString
方法,对于很多没有重写toString
方法的对象/类来说,则是不可读的object
-
代码无法非常复杂
如果需要写表达式,那么有一个 一行 的限制
换句话说三元式可用,
if/else
不可用 -
无法赋值或创建新的变量
以面用的例子来说
{``{ serverId = 20 }}
是会直接报错的: -
调用的函数不能有副作用(side effect)
换言之,只能调用 getter,不能调用 setter
-
安全性问题
像 React 一样,Angular 也会清理从 ViewModel 传向 View 层的数据
但是如果同时使用 string interpolation 和
bypassSecurityTrust
,那么当前代码就不会被清洗,如果中间有一些比较危险的代码,那么就会引起安全性的问题举例说明就是,如果当前应用有一个功能是去渲染用户之前留下的 comment,这里决定使用 string interpolation 去渲染用户的留言,而开发者假设用户的数据一定是干净的(后段已经进行过处理),所以决定使用
bypassSecurityTrust
后端也是这么觉得的,因此并没有清理用户数据
用户的数据里包含了攻击代码------如窃取当前网页中的 JWT token,自动在后台运行来自其他域名的攻击脚本等
那么就会引发这个安全性的问题,这个情况类似于 React 中直接食用
dangerouslySetInnerHTML
property binding
string interpolation 相当于是在页面上渲染一段文字,有的时候则需要更加动态的控制 DOM 元素的属性,比较常见的案例有,在发送了验证短信后一分钟内按钮呈现 disabled 的状态,或是大部分 input 元素中的 value 等,这些都无法使用 string interpolation 来解决,还是需要使用另一个不同的语法,也就是 property binding,用法如下:
-
依旧在
___.component.ts
中声明对应的变量tsexport class ServersComponent { allowNewServer = false; constructor() { setInterval(() => { console.log( new Date().toISOString(), 'allowNewServer: ', this.allowNewServer ); this.allowNewServer = !this.allowNewServer; }, 2000); } }
这里的设定是每 2s 将
allowNewServer
的值翻转一下 -
在 HTML template 中使用
[attribute]='var/method()'
的方式调用html<button class="btn btn-primary" [disabled]="!allowNewServer"> Add Server </button>
这里还没有新增
Add Server
的功能,这里主要是看disable
的状态⚠️:
var/method()
为一个表达式
效果为:

本质上来说,如果只是渲染一段文字的话,使用 string interpolation 会比较方便,如果是要绑定属性的话,则是使用 property binding,原因是二者没法互用,如下面的例子:
html
<p>{{ allowNewServer }}</p>
<p [innerText]="allowNewServer"></p>
的效果是一样的,但是混用就会报错:

事件绑定
数据从 View 层传输到 ViewModel 层,其绑定的方式与 property binding 相似:
-
VM 层实现一个事件
tsexport class ServersComponent { serverCreationStatus = 'No server was created!'; onCreateServer() { this.serverCreationStatus = 'Server was created!'; } }
-
V 层绑定该事件
html<button class="btn btn-primary" [disabled]="!allowNewServer" (click)="onCreateServer()" > Add Server </button>
效果:

传递 event 对象
这里需要对 event
事件对象进行绑定,View 层修改如下:
html
<label for="server-name">Server Name: {{ serverName }}</label>
<input
type="text"
class="form-control"
id="server-name"
(input)="onUpdateServerName($event)"
/>
这时候就可以在 ViewModel 中接收到 $event
了:
ts
onUpdateServerName($event: Event) {
this.serverName = (<HTMLInputElement>$event.target).value;
}
此时的效果为:

其实到此的实现和 React 还是挺像的,V 层调用 VM 层的表达式,将事件对象传到 VM 层;VM 层处理 business logic,将修改过的代码反映到 V 层上。不过下一个双向绑定就能确实的展现 React 和 Angular 在数据传输上的区别了。
⚠️:这里变量名称使用 $event
是一个预定俗称的规则(convention),可以改成其他的名称
❗:注意这里的 value
没有通过 property binding 实现绑定,所以这里的数据显示的是 input 里的数据
双向绑定
‼️:在使用双向绑定前必须要先在 AppModule
中导入 FormsModule
,如:
ts
// 导入 FormsModule
import { FormsModule } from '@angular/forms';
@NgModule({
// 新增 FormsModule
imports: [BrowserModule, FormsModule],
})
export class AppModule {}
接下来就可以使用 ngModel
了,具体使用如下:
-
修改 V 层代码
html<input type="text" class="form-control" id="server-name" [(ngModel)]="serverName" />
VM 层则不需要修改,最终效果如下:

乍一看好像 2 way databinding 和之前的实现没什么区别,不过如果将二者代码同时渲染,并且修改一下 serverName
的默认值,就能看到区别了:

-
使用
[(ngModel)]
同时兼具了 event binding 和 property binding对比起来,不使用 2 way binding 想要达成以下效果则需要这样的实现:
html<input type="text" class="form-control" id="server-name" (input)="onUpdateServerName($event)" [value]="serverName" />
-
数据的同步方式不一样
使用 property binding+event binding 的效果看起来和 2 way binding 一样,不过实际上它还是通过把值从 VM 层传到 V 层进行数据渲染,V 层调用 VM 层的 change handler 去实现数据变更,因此本质上它的 flow 还是 VM 层到 V 层的单方向实现
2 way binding 则会让 VM 层监听 V 层的变化,因此当 V 层的数据变化时,VM 层的数据也会同时进行更新。而如果其他地方也有 change handler 修改了 VM 层中的数据,则 V 层也能监听到 VM 层的变化,同时更新数据