angularJS interpolation插值和数据绑定
本文是翻译文章,翻译自angularJS插值函数和数据绑定。
插值器和数据绑定
angualrJS通过内嵌表达式形式的插值(interpolation)对文本节点和属性值进行数据绑定。
下面是一个插值的例子:
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a> |
文本绑定和属性绑定是怎么运行的
在编译过程中,编译器通过"$interpolate"服务查看文本节点和元素属性中通过嵌入表达式实现的插值标签。
在这之后,编译器在元素上添加一个插值指令并且在计算插值的函数中注册一个“watch”检测器。然后在脏检查循环中会更新对应的文本节点和属性值。
字符串表达式是如何计算的
如果插入的值不是字符串,那么他会按照下面的步骤计算:
@1:"undefined"和"null"转变成‘’空字符串。
@2:如果值是一个对象,并且不是Number,Date和Array,$interpolate查找这个对象的特定toString()方法,并且执行它。特定toString()表示myObejct.toString!=Object.prototype.toString。
@3:如果上面的过程没有执行,那么就调用JSON.stringify。
绑定布尔值属性
像"disabled"一类的属性会被计算成布尔属性,因为非空值表示“true”,而且他们默认值是“false”。我们不能使用一般的属性绑定方式绑定他们,因为HTML规范不需要浏览器保存boolean属性。这表示如果我们使用angularJS插值表达式设置这类属性,那么绑定的信息就会丢失。因为浏览器会忽略属性的值。
在下面的例子中,插值信息会被忽略并且浏览器会立即直接解释属性值,意味着这个button将会一直是disabled。
Disabled: <input type="checkbox" ng-model="isDisabled" /><button disabled="{{isDisabled}}">Disabled</button> |
对于这种情况,angularJS提供了一个特定的ng-prefixed指令来处理这些属性:disabled,required,selected,checked,readOnly,open。
这些指令指定一个属性的表达式,并且在表达式计算结果为真时才会设置对应的布尔属性为true。
Disabled: <input type="checkbox" ng-model="isDisabled" /><button ng-disabled="isDisabled">Disabled</button> |
ngAttr绑定特定属性
web浏览器有时特别挑剔的对属性值判定有效性。
例如,判定下面的模板:
<svg> <circle cx="{{cx}}"></circle></svg> |
我们期望angularJS可以绑定这个属性,但是当我们检查浏览器的console控制台时,我们看到这种错误信息:
Error: Invalid value for attribute cx="{{cx}}" |
因为SVG API的限制,我们不能简单的写 cx="{{cx}}", 通过”ng-attr-cx“你可以绕过这个问题。
如果一个属性通过“ngAttr”前缀绑定,那么在绑定期间将会把没有前缀的属性应用到对应的属性上。这种方式允许你绑定其他浏览器会立刻执行的属性(例如,SVG元素的circle[cx]属性)。在使用"ngAttr"时,“$interpolate”的“allOrNothing”标识符会被使用,所以如果字符串插值的计算结果是undefined,这个属性将会被移除并且不会添加到元素上。
例如,我们可以通过替代写法实现上面的例子:
<svg> <circle ng-attr-cx="{{cx}}"></circle></svg> |
如果要修改驼峰写法的属性(SV元素会检测驼峰写法属性的有效性),例如svg元素的“viewBox”,我们可以使用下划线来代替,这样绑定的属性是正常的驼峰写法。
例如,绑定“viewBox”,我们这样写:
<svg ng-attr-view_box="{{viewBox}}"></svg> |
其他的一些属性包含插值表达式时也不会按照我们预期的那样运行,我们也可以使用"ngAttr"来代替。下面是一些已知的有问题的属性列表:
@1:<select>的size属性。
@2:IE10/11的<textarea>的placeholder属性。
@3:IE11的<button>的type属性。
@3:IE11的<progress>的value属性。
已知问题
动态改变内插值
你应该避免动态改变内插值字符串的内容(例如 属性值或者文本值)。当原始字符串已经被运算,你对插值的修改可能会被覆盖。不推荐通过javascript直接修改内容,或通过directive指令间接修改内容。
例如,你不应该同时使用"style"(style="color:{{'orange'}};font-weight:{{'bold'}};")属性的插值和像“ngStyle”之类的指令修改"style"属性。
表达式中的内插值标签
注意:angularJS指令属性包含表达式或者内嵌表达式插值标签,这对于表达式的内插值标签是一种很烂的用法。
<div ng-show="form{{$index}}.$invalid"></div> |
你应该将这种复杂的表达式委托给scope,像这样:
<div ng-show="getForm($index).$invalid"></div> |
function getForm(index) { return $scope['form' + index];} |
你也可以在模板中通过"this"代表“scope”:
<div ng-show="this['form' + $index].$invalid"></div> |
为什么混合插值和表达式是一种坏习惯
@1:它会增加标签的复杂度。
@2:因为插值本身也是一个指令,所以不保证每一个标签都能正常运行。如果一个指令在插值运行计算之前就获取了属性值,那么指令获得的是没有转化成数据的原始插值。
@3:它会影响性能,因为插值器也会在scope上添加另一个“watch”绑定。
@4:所以这是一种不推荐的用法,我们不能对它进行测试,并且对于angularJS核心的改变可能会中断你的代码。