-
Notifications
You must be signed in to change notification settings - Fork 761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AngularJS实例教程(一)——数据绑定与监控 #14
Comments
明天晚上在苏宁大学课堂上面讲 |
飞哥真高产 |
赞~ |
好 |
赞 |
对angularjs的双向绑定有了重新认识。但是如果监控的变量非常多,对应用的性能影响是不是很大。 |
飞哥真高产 |
苏宁的非前端部门的兄弟们,欢迎加入我们前端协会,每周四晚上都有内部分享,不定期有面向所有体系的公开分享。 |
最近再看 ng,前来学习 |
mark一下,上班路上看。 |
很详细,非常感谢。 |
象棋单位的模块继承蛮巧的 |
@yibuyisheng 这个系列的后面会讲。之前我翻译的这篇你看过吗? https://github.com/xufei/Make-Your-Own-AngularJS/blob/master/01.md |
@soulteary 哪里巧了。。。04年写的那个就是这么写的,这部分基本无改变 |
昨天讲的过程中,有同事提出这么一些问题,记录一下:
此外,还让大家猜测了一下文中那个综合业务实例的JS代码行数,猜得最接近的也得到了一本书。 |
@xufei 不好意思,之前没看过,现在正在阅读,写的很给力,很详细 |
在angular源码中有这么一段注释:
个人感觉要不要在《构建自己的AngularJS,第一部分:Scope和Digest》中强调第二点。因为我之前不理解$evalSync,导致经常出现“$digest已经在执行”的异常,我想$evalSync能解决我的问题(根据注释第二点)。在《构建自己的AngularJS,第一部分:Scope和Digest》这篇文章中,个人感觉这块叙述的不是太清晰。 |
@xufei 我觉得棋子属性抽象蛮赞的,给我做的话,估计偷懒就绑定两个对象到作用域顶级对象上了... |
赞! |
@yibuyisheng 你的大部分问题应该可以简单粗暴地使用: $timeout(function() {
//aaa
//$scope.$digest();
}, 0); 来实现。 |
通熟易懂, 👍 |
@xufei 谢谢您的指导。根据您的指导,结合angular源码,基本理解了digest机制,这下应该不会再使用网上的那个"safeApply"了。 |
@yibuyisheng |
@atian25 目前个人认为可以不用$timeout,我的理解是: |
我的意思是,理解 |
@atian25 嗯,关键还是要理解digest原理,至于用什么解决方案,随自己喜好。谢谢你的耐心回复 |
@iamwucheng 因为ng-click内部帮你调用了一下。凡是数据想要更新到界面上,都必须通过$digest或者$apply,内置的一些东西不需要自己调用,是因为它们内部帮你调了,自己在指令里用原生事件操作的东西,需要手动调用。 |
@xufei 谢谢, 运行demo对比的时候有这样的猜想,但是找源码里面想找ng-click执行的过程相关的代码进行验证的时候就是找不到,在publishExternalAPI的方法里面并没有找到添加ng-click的逻辑(个人觉得应该会在添加指令的地方至少有,ng-click很显然应该是个指令) |
@iamwucheng https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js 这里统一处理了事件的指令 |
@dolymood 嗯, 看到了,谢谢, 最近的版本里面逻辑还变了点 |
@iamwucheng 看我这篇也可以:#10 |
@xufei 赞ヾ(´∀`)ノ |
学习了,点赞!!!!!!!!!!!! |
👏 👍 |
6得不行,赞一个! |
您好,请教一下,那个综合例子中,民族(editingEmployee.minority)是如何通过这个值,转化成汉族或者(editingEmployee.nation),我是在controller中增加format方法来完成的,我并没有在您的employee.js中看到有任何format方法,是有其他更简洁方法吗? |
@aicekiller 没有使用minority做显示的转换,这里只是用它来做了radio的选中和输入框的显示隐藏,真正的值在nation里,真实用的话,是需要做个format的,不然提交的时候不好办。 |
@xufei 谢谢 |
请教一个问题: <div ng-app="myApp" ng-controller="myCtrl">
<ul class="list-group">
<li class="list-group-item" ng-repeat="(id,x) in names" ng-click="item=li(id);selected.id=id">
{{x.name}}
</li>
<div>{{selected.id}}</div>
<div>{{item}}</div>
<div>{{str}}</div>
</ul>
</div> <script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.names = [{
name: 'a'
}, {
name: 'b'
}, {
name: 'c'
}];
$scope.selected = {
id: 0
};
$scope.item = 0;
$scope.li = function(n) {
$scope.item = n
};
});
</script>
请问上面这段代码我在li的ng-click事件中直接用item=id赋值的话为什么item值不会改变?用controller里面定义的li()方法赋值后就能获取到,或者对controller里面定义的selected对象赋值也能获取到,这是什么原因?谢谢 |
@manolitowang 隐约记得 ng-click 会创建一个local scope。 |
@yibuyisheng 谢谢,你是对的,我看到了相关的介绍 |
@yibuyisheng @manolitowang ng-click并不会创建一个新作用域。。。这里出现这个问题是因为ng-repeat创建了一个继承作用域,且里面是写操作所以不会更新外部的item |
@kuitos 噢,记得错了。刚看了源码,对于 ngClick 只是注入了一个 local 对象:https://github.com/angular/angular.js/blob/a4e60cb6970d8b6fa9e0af4b9f881ee3ba7fdc99/src/ng/directive/ngEventDirs.js#L64 ,并没有创建新 scope 。 |
写得真好!冒泡排序的实现貌似有点问题,似乎应该把i和j提出sort外,否则每次运行sort都需要从头开始排序,提出去后多个sort之间能保留上一次的状态。 |
非常感谢你,这篇教程写的真棒! |
看了ng-repeat的源码,想问一下您为什么在遍历collection的时候缺省使用'valueType: value'作为索引,这点我不是特别理解,求指点一下,谢谢。 @xufei |
看完菜鸟的教程,然后刷完大漠老师的视频,然后再看这些内容。哇,这种感觉太爽了 |
数据绑定与监控
在业务开发的过程中,我们可能会大量使用DOM操作,这个过程很繁琐,但是有了AngularJS,基本上就可以解脱了,做到这一点的关键是数据绑定。那什么是数据绑定,怎样绑定呢?本节将从多种角度,选取业务开发过程中的各种场景来举例说明。
基于单一模型的界面同步
有时候,我们会有这样的需求,界面上有个输入框,然后有另外一个地方,要把这个文本原样显示出来,如果没有数据绑定,这个代码可能很麻烦了,比如说,我们要监听输入框的各种事件,键盘按键、复制粘贴等等,然后再把取得的值写入对应位置。但是如果有数据绑定,这个事情就非常简单。
这么小小一段代码,就实现了我们想要的功能,是不是很可爱?这中间的关键是什么呢,就是变量a,在这里,变量a充当了数据模型的角色,输入框的数据变更会同步到模型上,然后再分发给绑定到这个模型的其他UI元素。
注意,任意绑定到数据模型的输入元素,它的数据变更都会导致模型变更,比如说,我们有另外一个需求,两个输入框,我们想要在任意一个中输入的时候,另外一个的值始终跟它保持同步。如果用传统的方式,需要在两个输入框上都添加事件,但是有了数据绑定之后,这一切都很简单:
这样的代码就可以了,核心要素还是这个数据模型b。
到目前为止的两个例子都很简单,但可能有人有问题要问,因为我们什么js都没有写,这个a跟b是哪里来的,为什么就能起作用呢?对于这个问题,我来作个类比。
比如大家写js,都知道变量可以不声明就使用:
这时候a被赋值到哪里了呢,到了全局的window对象上,也就是说其实相当于:
在AngularJS里,变量和表达式都附着在一种叫做作用域(scope)的东西上,可以自己声明新的作用域,也可以不声明。每个Angular应用默认会有一个根作用域($rootScope),凡是没有预先声明的东西,都会被创建到它上面去。
作用域的相关概念,我们会在下一章里面讲述。在这里,我们只需要知道,如果在界面中绑定了未定义的某变量,当它被赋值的时候,就会自动创建到对应的作用域上去。
前面我们在例子中提到的{{}}这种符号,称为插值表达式,这里面的内容将会被动态解析,也可以不使用这种方式来进行绑定,Angular另有一个ng-bind指令用于做这种事情:
对模型的二次计算
嗯,有时候,实际情况没有这么简单,比如说,我们可能会需要对数据作一点处理,比如,在每个表示价格的数字后面添加一个单位:
当然我们这个例子并不好,因为,其实你可以把无关的数据都放在绑定表达式的外面,就像这样:
那么,考虑个稍微复杂一些的。我们经常会遇到,在界面上展示性别,但是数据库里面存的是0或者1,那么,总要对它作个转换。有些比较老土的做法是这样,在模型上添加额外的字段给显示用:
这是原始数据:
被他转换之后,成了这样:
转换函数内容如下:
这样的做法虽然能够达到效果,但破坏了模型的结构,我们可以做些改变:
这样我们就达到了目的。这个例子让我们发现,原来,在绑定表达式里面,是可以使用函数的。像我们这里的格式化函数,其实作用只存在于视图层,所以不会影响到真实数据模型。
注意:这里有两个注意点。
第一,在绑定表达式里面,只能使用自定义函数,不能使用原生函数。举个例子:
这句就是有问题的,因为Angular的插值表达式机制决定了它不能使用这样的函数,它是直接用自己的解释器去解析这个字符串的,如果确实需要调用原生函数,可以用一个自定义函数作包装,在自定义函数里面可以随意使用各种原生对象,就像这样:
第二,刚才我们这个例子只是为了说明可以这么用,但不表示这是最佳方案。Angular为这类需求提供了一种叫做filter的方案,可以在插值表达式中使用管道操作符来格式化数据,这个我们后面再细看。
数组与对象结构的绑定
有时候,我们的数据并不总是这么简单,比如说,有可能会需要把一个数组的数据展示出来,这种情况下可以使用Angular的ng-repeat指令来处理,这个东西相当于一个循环,比如我们来看这段例子:
这样就可以把数组的内容展示到界面上了。数组中的数据产生变化时,也能够实时更新到界面上来。
有时候,我们会遇到数组里有重复元素的情况,这时候,ng-repeat代码不能起作用,原因是Angular默认需要在数组中使用唯一索引,那假如我们的数据确实如此,怎么办呢?可以指定它使用序号作索引,就像这样:
也可以把多维数组用多层循环的方式迭代出来:
如果是数组中的元素是对象结构,也不难,我们用个表格来展示这个数组:
有时候我们想遍历对象的属性,也可以使用ng-repeat指令:
注意,在ng-repeat表达式里,我们使用了一个(key, value)来描述键值关系,如果只想要值,也可以不用这么写,直接按照数组的写法即可。对象值有重复的话,不用像数组那么麻烦需要指定$index做索引,因为它是对象的key做索引,这是不会重复的。
数据监控
有时候,我们不是直接把数据绑定到界面上,而是先要赋值到其他变量上,或者针对数据的变更,作出一些逻辑的处理,这个时候就需要使用监控。
最基本的监控很简单:
对作用域上的变量添加监控之后,就可以在变更时得到通知了。如果说新赋值的变量跟原先的相同,这个监控就不会被执行。比如说刚才例子中,继续对a赋值为1,不会进入监控函数。
以上这种方式可以监控到最直接的赋值,包括各种基本类型,以及复杂类型的引用赋值,比如说下面这个数组被重新赋值了,就可以被监控到:
但这种监控方式只能处理引用相等的判断,对于一些更复杂的监控,需要更细致的处理。比如说,我们有可能需要监控一个数组,但并非监控它的整体赋值,而是监控其元素的变更:
注意,这里我们在$watch函数中,添加了第三个参数,这个参数用于指示对数据的深层监控,包括数组的子元素和对象的属性等等。
样式的数据绑定
刚才我们提到的例子,都是跟数据同步、数据展示相关,但数据绑定的功能是很强大的,其应用场景取决于我们的想象力。
不知道大家有没有遇到过这样的场景,有一个数据列表,点中其中某条,这条就改变样式变成加亮,如果用传统的方式,可能要添加一些事件,然后在其中作一些处理,但使用数据绑定,能够大幅简化代码:
在本例中,我们使用了一个循环来迭代数组中的元素,并且使用一个变量selectedItem用于标识选中项,然后关键点在于这个ng-class的表达式,它能够根据当前项是否为选中项,作出一个判断,生成对应的样式名。这是绑定的一个典型应用了,基于它,能把一些之前需要依赖于某些控件的功能用特别简单的方式做出来。
除了使用ng-class,还可以使用ng-style来对样式作更细致的控制,比如:
状态控制
有时候,我们除了控制普通的样式,还有可能要控制某个界面元素的显示隐藏。我们用ng-class或者ng-style当然都是可以控制元素的显示隐藏的,但Angular给我们提供了一种快捷方式,那就是ng-show和ng-hide,它们是相反的,其实只要一个就可以了,提供两个是为了写表达式的方便。
利用数据绑定,我们可以很容易实现原有的一些显示隐藏功能。比如说,当列表项有选中的时候,某些按钮出现,当什么都没选的时候,不出现这些按钮。
主要的代码部分还是借用上面那个列表,只添加一些相关的东西:
把这个代码放在刚才的列表旁边,位于同一个controller下,点击列表元素,就能看到绑定状态了。
有时候,我们也想控制按钮的可点击状态,比如刚才的例子,那两个按钮直接显示隐藏,太突兀了,我们来把它们改成启用和禁用。
同理,如果是输入框,可以用同样的方式,使用ng-readonly来控制其只读状态。
流程控制
除了使用ng-show和ng-hide来控制元素的显示隐藏,还可以使用ng-if,但这个的含义与实现机制都大为不同。所谓的show和hide,意味着DOM元素已经存在,只是控制了是否显示,而if则起到了流程控制的作用,只有符合条件的DOM元素才会被创建,否则不创建。
比如下面的例子:
这个例子初始的时候,创建了三个li,其中一个被隐藏(show 2),当点击按钮,condition变成2,仍然是三个li,其中,if 1没有了,if 2创建出来了,show 1隐藏了,show 2显示了。
所以,我们现在看到的是,if的节点是动态创建的。与此类似,我们还可以使用ng-switch指令:
这个例子跟if基本上是一个意思,只是语法更自然些。
数据联动
在做实际业务的过程中,很容易就碰到数据联动的场景,最典型的例子是省市县的三级联动。很多前端教程或者基础面试题以此为例,综合考察其中所运用到的知识点。
如果是用Angular做开发,很可能这个就不成其为一个考点了,因为实现起来非常容易。
我们刚才已经实现了一个单级列表,可以借用这段代码,做两个列表,第一个的数据变动,对第二个的数据产生过滤。
这段代码看起来比刚才复杂一些,其实有价值的代码就那个$watch里面的东西。这是什么意思呢?意思是,监控selectedProvince这个变量,只要它改变了,就去查询它可能造成的更新数据,然后剩下的事情就不用我们管了。
如果是绑定到下拉框上,代码更简单,因为AngularJS专门作了这种考虑,ng-options就是这样的设置:
从这个例子我们看到,相比于传统前端开发方式那种手动监听事件,手动更新DOM的方式,使用数据绑定做数据联动简直太容易了。如果要把这个例子改造成三级联动,只需对selectedCity也做一个监控就行了。
一个综合的例子
了解了这些细节之后,我们可以把它们结合起来做一个比较实际的综合例子。假设我们在为一家小店创建雇员的管理界面,其中包含一个雇员表格,以及一个可用于添加或编辑雇员的表单。
雇员包含如下字段:姓名,年龄,性别,出生地,民族。其中,姓名通过输入框输入字符串,年龄通过输入框输入整数,性别通过点选单选按钮来选择,出生地用两个下拉框选择省份和城市,民族可以选择汉族和少数民族,如果选择了少数民族,可以手动输入民族名称。
这个例子恰好能把我们刚才讲的绑定全部用到。我们先来看看有哪些绑定关系:
如果想做得精细,还有更多可以使用绑定的地方,不过上面这些已经足够我们把所有知识用一遍了。
这个例子的代码就不贴了,可以自行查看。
数据绑定的拓展运用
现在我们学会了数据绑定,可以借助这种特性,完成一些很别致的功能。比如说,如果想在页面上用div模拟一个正弦波,只需要把波形数据生成出来,然后一个绑定就可以完成了。
如果我们想让这个波形动起来,也很容易,只需要结合一个定时器,动态生成这个波形数据就可以了。为了形成滚动效果,当波形采点数目超过某个值的时候,可以把最初的点逐个拿掉,保持总的数组长度。
这个例子对应的HTML代码如下:
有时候我们经常看到一些算法可视化的项目,比如把排序算法用可视化的方式展现出来,如果使用AngularJS的数据绑定,实现这种效果可谓易如反掌:
看,就这么简单,一个冒泡排序算法的可视化过程就写好啦。
甚至,AngularJS还允许我们在SVG中使用数据绑定,使用它,做一些小游戏也是很容易的,比如我写了个双人对战的象棋,这里有演示地址,可以查看源码,是不是很简单?
小结
刚才我们已经看到数据绑定的各种使用场景了,这个东西带给我们的最大好处是什么呢?我们回顾一下之前写Web界面,有一部分时间在写HTML和CSS,一部分时间在写纯逻辑的JavaScript,还有很多时间在把这两者结合起来,比如各种创建或选取DOM,设置属性,添加等等,这些事情都是很机械而繁琐的,数据绑定把这个过程简化了,代码也跟着清晰了。
数据绑定是一种思维方式,一切的核心就是数据。数据的变更导致界面的更新,如果我们想要更新界面,只需改变数据即可。
本章所涉及的所有Demo,参见在线演示地址
代码库
演讲幻灯片下载:点这里
The text was updated successfully, but these errors were encountered: