[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 中调用即可。用法如下:
-
在组件中声明变量/方法
export class ServerComponent { serverId = 10; serverStatus = 'offline'; getServerStatus() { return this.serverStatus; } }
-
使用
{{ var/method() }}
的方式调用<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
中声明对应的变量export 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()'
的方式调用<button class="btn btn-primary" [disabled]="!allowNewServer"> Add Server </button>
这里还没有新增
Add Server
的功能,这里主要是看disable
的状态⚠️:
var/method()
为一个表达式
效果为:
本质上来说,如果只是渲染一段文字的话,使用 string interpolation 会比较方便,如果是要绑定属性的话,则是使用 property binding,原因是二者没法互用,如下面的例子:
<p>{{ allowNewServer }}</p>
<p [innerText]="allowNewServer"></p>
的效果是一样的,但是混用就会报错:
事件绑定
数据从 View 层传输到 ViewModel 层,其绑定的方式与 property binding 相似:
-
VM 层实现一个事件
export class ServersComponent { serverCreationStatus = 'No server was created!'; onCreateServer() { this.serverCreationStatus = 'Server was created!'; } }
-
V 层绑定该事件
<button class="btn btn-primary" [disabled]="!allowNewServer" (click)="onCreateServer()" > Add Server </button>
效果:
传递 event 对象
这里需要对 event
事件对象进行绑定,View 层修改如下:
<label for="server-name">Server Name: {{ serverName }}</label>
<input
type="text"
class="form-control"
id="server-name"
(input)="onUpdateServerName($event)"
/>
这时候就可以在 ViewModel 中接收到 $event
了:
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
,如:
// 导入 FormsModule
import { FormsModule } from '@angular/forms';
@NgModule({
// 新增 FormsModule
imports: [BrowserModule, FormsModule],
})
export class AppModule {}
接下来就可以使用 ngModel
了,具体使用如下:
-
修改 V 层代码
<input type="text" class="form-control" id="server-name" [(ngModel)]="serverName" />
VM 层则不需要修改,最终效果如下:
乍一看好像 2 way databinding 和之前的实现没什么区别,不过如果将二者代码同时渲染,并且修改一下 serverName
的默认值,就能看到区别了:
-
使用
[(ngModel)]
同时兼具了 event binding 和 property binding对比起来,不使用 2 way binding 想要达成以下效果则需要这样的实现:
<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 层的变化,同时更新数据