6. 数据绑定与模板
我纠结了半天,“数据绑定”与“模板”这两个东西还真没办法分开来说。因为数据绑定需要以模板为载体,离开了模板,数据还绑个毛啊。
ng 的一大特点,就是数据双向绑定。双向绑定是一体,为了描述方便,下面分别介绍。
6.1. 数据->模板
数据到表现的绑定,主要是使用模板标记直接完成的:
1 | <p>{{ w }} x {{ h }}</p> |
使用 这个标记,就可以直接引用,并绑定一个作用域内的变量。在实现上, ng 自动创建了一个 watcher 。效果就是,不管因为什么,如果作用域的变量发生了改变,我们随时可以让相应的页面表现也随之改变。我们可以看一个更纯粹的例子:
1 | <p "test" ng-controller="TestCtrl">{{ a }}</p> |
上面的例子在页面载入之后,我们可以在页面上看到 123 。这时,我们可以打开一个终端控制器,输入:
1 | $('#test').scope().a = '12345'; |
上面的代码执行之后,就可以看到页面变化了。
对于使用 ng 进行的事件绑定,在处理函数中就不需要去关心 $digest()
的调用了。因为 ng 会自己处理。源码中,对于 ng 的事件绑定,真正的处理函数不是指定名字的函数,而是经过 $apply()
包装过的一个函数。这个 $apply()
做的一件事,就是调用根作用域 $rootScope
的 $digest()
,这样整个世界就清净了:
1 | <p "test" ng-controller="TestCtrl" ng-click="click()">{{ a }}</p> |
那个 click 函数的定义,绑定时变成了类似于:
1 | function(){ |
这里的 $scope.$apply()
中做的一件事:
1 | $rootScope.$digest(); |
6.2. 模板->数据
模板到数据的绑定,主要是通过 ng-model 来完成的:
1 | <input type="text" id="test" ng-controller="TestCtrl" ng-model="a" /> |
这时修改 input 中的值,然后再在控制终端中使用:
1 | $('#test').scope().a |
查看,发现变量 a 的值已经更改了。
实际上, ng-model 是把两个方向的绑定都做了。它不光显示出变量的值,也把显示上的数值变化反映给了变量。这个在实现上就简单多了,只是绑定 change 事件,然后做一些赋值操作即可。不过 ng 里,还要区分对待不同的控件。
6.3. 数据->模板->数据->模板
现在要考虑的是一种在现实中很普遍的一个需求。比如就是我们可以输入数值,来控制一个矩形的长度。在这里,数据与表现的关系是:
长度数值保存在变量中
变量显示于某个 input 中
变量的值即是矩形的长度
input 中的值变化时,变量也要变化
input 中的值变化时,矩形的长度也要变化
当然,要实现目的在这里可能就不止一种方案了。按照以前的做法,很自然地会想法,绑定 input 的 change 事件,然后去做一些事就好了。但是,我们前面提到过 ng-model 这个东西,利用它就可以在不手工处理 change 的条件下完成数据的展现需求,在此基础之上,我们还需要做的一点,就是把变化后的数据应用到矩形的长度之上。
最开始,我们面对的应该是这样一个东西:
1 | <div ng-controller="TestCtrl"> |
我们从响应数据变化,但又不使用 change 事件的角度来看,可以这样处理宽度变化:
1 | <script> |
使用 $watch()
来绑定数据变化。
当然,这种样式的问题,有更直接有效的手段, ng 的数据绑定总是让人惊异:
1 | <div ng-controller="TestCtrl"> |
前面讲了数据绑定之后,现在可以单独讲讲模板了。
作为一套能称之谓“模板”的系统,除了能干一些模板的常规的事之外(好吧,即使是常规的逻辑判断现在它也做不了的),配合作用域 $scope
和 ng 的数据双向绑定机制, ng 的模板系统就变得比较神奇了。
7.1. 定义模板内容
定义模板的内容现在有三种方式:
在需要的地方直接写字符串
使用 script 标签定义的“内部文件”
第一种不需要多说。第二种和第三种都可以和 ng-include 一起工作,来引入一段模板。
直接引入同域的外部文件作为模板的一部分:
1 | <div ng-include src="'tpl.html'"> |
注意, src 中的字符串会作为表达式处理(可以是 $scope
中的变量),所以,直接写名字的话需要使用引号。
引入 script 定义的“内部文件”:
1 | <script type="text/ng-template" id="tpl"> |
配合变量使用:
1 | <script type="text/ng-template" id="tpl"> |
7.2. 内容渲染控制
7.2.1. 重复 ng-repeat
这算是唯一的一个控制标签么……,它的使用方法类型于:
1 | <div ng-controller="TestCtrl"> |
除此之外,它还提供了几个变量可供使用:
$index
当前索引$first
是否为头元素$middle
是否为非头非尾元素$last
是否为尾元素
1 | <div ng-controller="TestCtrl"> |
7.2.2. 赋值 ng-init
这个指令可以在模板中直接赋值,它作用于 angular.bootstrap 之前,并且,定义的变量与 $scope
作用域无关。
1 | <div ng-controller="TestCtrl" ng-init="a=[1,2,3,4];"> |
7.3. 节点控制
7.3.1. 样式 ng-style
可以使用一个结构直接表示当前节点的样式:
1 | <div ng-style="{width: 100 + 'px', height: 100 + 'px', backgroundColor: 'red'}"> |
同样地,绑定一个变量的话,威力大了。
7.3.2. 类 ng-class
就是直接地设置当前节点的类,同样,配合数据绑定作用就大了:
1 | <div ng-controller="TestCtrl" ng-class="cls"> |
ng-class-even 和 ng-class-odd 是和 ng-repeat 配合使用的:
1 | <ul ng-init="l=[1,2,3,4]"> |
注意里面给的还是表示式,别少了引号。
7.3.3. 显示和隐藏
ng-show ng-hide ng-switch 前两个是控制 display 的指令:
1 | <div ng-show="true">1</div> |
后一个 ng-switch 是根据一个值来决定哪个节点显示,其它节点移除:
1 | <div ng-init="a=2"> |
7.3.4. 其它属性控制
ng-src 控制 src 属性:
1 | <img ng-src="{{ 'h' + 'ead.png' }}" /> |
ng-href 控制 href 属性:
1 | <a ng-href="{{ '#' + '123' }}">here</a> |
总的来说:
ng-src src 属性
ng-href href 属性
ng-checked 选中状态
ng-selected 被选择状态
ng-disabled 禁用状态
ng-multiple 多选状态
ng-readonly 只读状态
注意: 上面的这些只是单向绑定,即只是从数据到展示,不能反作用于数据。要双向绑定,还是要使用 ng-model 。
7.4. 事件绑定
事件绑定是模板指令中很好用的一部分。我们可以把相关事件的处理函数直接写在 DOM 中,这样做的最大好处就是可以从 DOM 结构上看出业务处理的形式,你知道当你点击这个节点时哪个函数被执行了。
ng-change
ng-click
ng-dblclick
ng-mousedown
ng-mouseenter
ng-mouseleave
ng-mousemove
ng-mouseover
ng-mouseup
ng-submit
对于事件对象本身,在函数调用时可以直接使用 $event 进行传递:
1 | <p ng-click="click($event)">点击</p> |
7.5. 表单控件
表单控件类的模板指令,最大的作用是它预定义了需要绑定的数据的格式。这样,就可以对于既定的数据进行既定的处理。
7.5.1. form
form 是核心的一个控件。 ng 对 form 这个标签作了包装。事实上, ng 自己的指令是叫 ng-form 的,区别在于, form 标签不能嵌套,而使用 ng-form 指令就可以做嵌套的表单了。
form 的行为中依赖它里面的各个输入控制的状态的,在这里,我们主要关心的是 form 自己的一些方法和属性。从 ng 的角度来说, form 标签,是一个模板指令,也创建了一个 FormController 的实例。这个实例就提供了相应的属性和方法。同时,它里面的控件也是一个 NgModelController 实例。
很重要的一点, form 的相关方法要生效,必须为 form 标签指定 name 和 ng-controller ,并且每个控件都要绑定一个变量。 form 和控件的名字,即是 $scope
中的相关实例的引用变量名。
1 | <form name="test_form" ng-controller="TestCtrl"> |
除去对象的方法与属性, form 这个标签本身有一些动态类可以使用:
ng-valid 当表单验证通过时的设置
ng-invalid 当表单验证失败时的设置
ng-pristine 表单的未被动之前拥有
ng-dirty 表单被动过之后拥有
form 对象的属性有:
$pristine
表单是否未被动过$dirty
表单是否被动过$valid
表单是否验证通过$invalid
表单是否验证失败$error
表单的验证错误
其中的 $error
对象包含有所有字段的验证信息,及对相关字段的 NgModelController 实例的引用。它的结构是一个对象, key 是失败信息, required , minlength 之类的, value 是对应的字段实例列表。
注意,这里的失败信息是按序列取的一个。比如,如果一个字段既要求 required ,也要求 minlength ,那么当它为空时,$error
中只有 required 的失败信息。只输入一个字符之后, required 条件满足了,才可能有 minlength 这个失败信息。
1 | <form name="test_form" ng-controller="TestCtrl"> |
7.5.2. input
input 是数据的最主要入口。 ng 支持 HTML5 中的相关属性,同时对旧浏览器也做了兼容性处理。最重要的, input 的规则定义,是所属表单的相关行为的参照(比如表单是否验证成功)。
input 控件的相关可用属性为:
name 名字
ng-model 绑定的数据
required 是否必填
ng-required 是否必填
ng-minlength 最小长度
ng-maxlength 最大长度
ng-pattern 匹配模式
ng-change 值变化时的回调
1 | <form name="test_form" ng-controller="TestCtrl"> |
input 控件,它还有一些扩展,这些扩展有些有自己的属性:
input type=”number” 多了 number 错误类型,多了 max , min 属性。
input type=”url” 多了 url 错误类型。
input type=”email” 多了 email 错误类型。
7.5.3. checkbox
它也算是 input 的扩展,不过,它没有验证相关的东西,只有选中与不选中两个值:
1 | <form name="test_form" ng-controller="TestCtrl"> |
两点:
controller 要初始化变量值。
controller 中的初始化值会关系到控件状态(双向绑定)。
7.5.4. radio
也是 input 的扩展。和 checkbox 一样,但它只有一个值了:
1 | <form name="test_form" ng-controller="TestCtrl"> |
7.5.5. textarea
同 input 。
7.5.6. select
这是一个比较牛B的控件。它里面的一个叫做 ng-options 的属性用于数据呈现。
对于给定列表时的使用。
最简单的使用方法, x for x in list :
1 | <form name="test_form" ng-controller="TestCtrl" ng-init="o=[0,1,2,3]; a=o[1];"> |
在 $scope
中, select 绑定的变量,其值和普通的 value 无关,可以是一个对象:
1 | <form name="test_form" ng-controller="TestCtrl" |
显示与值分别指定, x.v as x.name for x in o :
1 | <form name="test_form" ng-controller="TestCtrl" |
加入分组的, x.name group by x.g for x in o :
1 | <form name="test_form" ng-controller="TestCtrl" |
分组了还分别指定显示与值的, x.v as x.name group by x.g for x in o :
1 | <form name="test_form" ng-controller="TestCtrl" |
如果参数是对象的话,基本也是一样的,只是把遍历的对象改成 (key, value) :
1 | <form name="test_form" ng-controller="TestCtrl" ng-init="o={a: 0, b: 1}; a=o.a;"> |
关注 web翎云阁,定时推送,互动精彩多,若你有更好的见解,欢迎留言探讨!