Angularjs2 再谈数据绑定

在开始之前,我们先做一个简单的测试,假设有如下HTML片段:

测试html代码
1
<input id="inDemo" value="Hello">

我们不难想象,运行后页面上会出现一个单行文本输入框,然后默认的值为 Hello。然后我们可以通过开发者工具的Console来执行一些临时的脚本,我们先获取这个元素:

获取测试元素
1
var inDemo = document.getElementById('inDemo');

然后接着操作,在界面上我们将输入框的Hello,修改为Angular,然后在Console中通过js获取input的value:

获取input的value值
1
console.log(inDemo.value);

毋容置疑,会输出字符串Angular,那么,如果继续测试,执行下面的语句会输出什么呢?

获取input的attribute值
1
console.log(inDemo.getAttribute('value'));

这里的结果会输出 Hello,那么这里的.value.getAttribute('value') 区别在哪里呢? 这个就是我们这个测试的目的,也就是下面将要说明的 HTML Attribute DOM Property

HTML Attribute DOM Property 的区别

Attribute 和 Property 的翻译都是属性的意思,加上完整的语义,那么可以表示为 HTML属性和 DOM属性,那么为什么会有这两个都是属性的东西存在呢?这个就要先简单了解下浏览器的工作方式。

浏览器会将获取到的HTML文本(网页),通过解析HTML文件生成DOM树,如果有接触过其他编程语言应该会了解到树型结构的数据,也就是一些语言中的NodeTreeNode等,没错,在Javascript中,DOM树中元素会被创建成一个HTMLElement对象,而这个HTMLElement对象也包含有一些属性,这些属性就是上面说的DOM Property,而HTMLElement对象一些属性的初始值则是通过HTML Attribute来的。

那么整体的逻辑应该是这样的:

  • 一段HTML片段<input id="inDemo" value="Hello">
  • 浏览器解析创建一个HTMLElement对象
  • 根据HTML Attribute初始化对象属性,id=inDemo,value=Hello

其实这个思维,我们在一些其他的开发中也有用到过,比如我们的程序在启动时需要读取配置文件,然后将配置文件中定义的属性值,转换为运行时的对象,从而在程序中来使用,如果在运行时修改配置对象的值,而不会影响配置文件的值,HTML AttributeDOM Property和这种方式可以看做是一致的。

我们可以看一下创建的对象都有哪些属性,接着测试,在Console窗口中执行:console.dir(inDemo)
创建的HTMLElement对象

可以看到,会拥有非常多的属性和事件,其中的id也已经被赋值为inDemo, 蓝色框的disabled:false可以关注下,后面会介绍这个属性。

了解这些的意义是什么? 其实了解这些,只是需要明白一点,现阶段的前端框架,附带的数据双向绑定功能,Attribute只是初始化了元素状态,打交道的都是DOM Property,而我们在javascript中使用的也是DOM Property,只有明白了这点,才能更好的理解以及避免出现的一些问题,比如,常用的元素禁用,有时候我们为了禁用一个元素会这样写:

禁用input
1
<input id="inDemo" value="Hello" disabled="false">

这个渲染出来的文本框是禁用的还是可用的呢? 实际效果是禁用状态,可是明明给disabled赋值是false的呀。其实HTMLElement对象的默认disabled属性值是false,然后 HTML Attribute中只要出现了disabled就会将HTMLElement对象的disabled设置为true,和HTML Attributedisabled取值无关。

总的来讲,attribute 是由 HTML 定义的,property 是由 DOM(Document Object Model) 定义的,且具有一些关系:

  • 少量 HTML AttributeProperty 之间有着 1:1 的映射,比如:idnamevalue
  • 有些 HTML Attribute 没有对应的 Property,比如:colspan
  • 有些 DOM Property 没有对应的 Attribute,比如:textContent
  • 大量 HTML Attribute 看起来像是映射到了 Property,但是并不是我们想的那样。

使用 NgModel 完成数据双向绑定

在上一篇我们简单提到,在input中的数据双向绑定使用[(ngModel)]="",在Angularjs2中,使用ngModel进行双向绑定之前,需要导入FormsModule模块。

在开始介绍[(ngModel)]之前,我们先简单的了解下数据绑定。通俗的理解就是:将当前上下文中数据绑定给模板或者指令属性等。那么既然是绑定就需要了解绑定涉及到的两个概念:绑定目标绑定数据源

绑定的规则是:目标 = 源,其实我们可以理解为赋值,变量的赋值。

绑定的目标是绑定符:[]()[()] 中的属性或者事件名,且只能绑定到显示标记为输入[]输出()的属性,这点后面会做下说明。

接着回到[(ngModel)],我们可以看到包裹ngModel既是输入又是输出,其实这是一个语法糖,ngModel指令的完整写法为:

ngModel完整写法
1
<input [ngModel]="name" (ngModelChange)="name=$event">

一般建议使用封装好的[(ngModel)]的方式,如果需要在用户输入的时候做些转换处理,则第二种是很方便的。当然,也可以直接利用 <input> 元素的value属性和input事件来实现绑定效果:

实现绑定
1
<input [value]="name" (input)="name=$event.target.value">

具体在指令的学习时会再次深入ngModel指令进行分析,接下来简单聊下 输入(@Input)输出(@Output)

这里借用官方的一张图:
Input和Output的示意

从图中可以看出,一般属性的赋值会被称作为输入,表示数据从当前的上下文中流入到指定的指定上。
而事件的赋值则被称为输出,表示事件调用会执行外部的处理器。

指令的学习中会详细的涉及到@Input()@Output()