最近在写AngularJS,遇到一个问题,在Ajax异步请求后台数据,然后将结果赋值给当前scope中某对象的属性,在页面中怎么都取不到,然而在js端却可以正常打印出来。
分析原因:第一感觉是前端页面绑定指令不对,导致不能正常显示,然而变化各种指令都不能正常获取,很是郁闷;最后去掉Ajax,直接返回给页面,结果却是可以的,初步排除了与绑定指令相关。那问题出现在scope上了???结果查阅资料,终于得知,使用第三方框架(比如jQuery),或者调用setTimeout(),会导致其运行在AngularJS上下文外部,可以使用apply()函数让Angular返回digest循环,传递到Angular应用中。
一、传统事件触发
在标准的浏览器流程中,页面加载、$http请求返回响应、鼠标移动以及按钮被点击等情况都会触发事件。当事件被触发时(比如点击一个链接),JavaScript会创建一个事件对象,并执行这个事件对象所在的监听特定事件的所有函数。然后浏览器会执行注册给该事件的回调函数,更新DOM。
注意:同一时间不能运行两个事件。
当使用angular时,其会扩展这个标准的浏览器流程,创建一个angular上下文(angular事件循环内的特定代码,该angular事件循环通常被称为$digest循环)。
二、$digest循环
digest循环有两个主要部分组成:watch列表,$evalAsync列表。
1. $watch列表
angular跟踪变化,是通过给watch列表添加一个监控函数做到的,需要注意的是所有绑定给同一scope对象的UI元素,只会添加一个watch到watch列表中。这些watch列表会在digest循环中的“脏值检查”(检测值是否发生了变化,但整个应用还没有同步该变化)的程序解析。
(1)$watch(watchExpression, listener/callback, objectEquality)
(2)$watchCollection(obj/string, listener)
2. $evalAsync列表
$evalAsync()方法是一种在当前作用域上调度表达式在未来某个时刻运行的方式。
指令、控制器调用$evalAsync(),会在angular操作DOM之后,浏览器渲染之前运行。所以,永远不要使用其来约定事件的顺序。
三、页面中的$digest循环
(1)angular会设置一个隐式的监控器,将输入字段的值绑定为当前的$scope对象;
(2)当用户输入字符,angular上下文就会生效并开始遍历$$watchers($watch列表);
(3)监控函数在$scope.user.name绑定上执行;
(4)退出$digest循环之前,会触发该值(ng-model)上运行的验证和格式化操作;
(5)由于在digest循环中值发生了变化,angular需要再次运行这一循环以确定它没有改变作用域对象上的其他值。(原因:如果有一个名为scope.user.fullName的属性由scope.user.firstName和$scope.user.lastName组成,那么这两个值的变化多会引起fullName的变化,因此需要再次确认);
(6)$digest循环退出,浏览器重绘DOM以刷新视图。
四、$apply从外部进入上下文
所有指令ng-[event]指令(如ng-click)都会调用scope.apply(),以强制运行$digest循环。
apply()函数可以从angular框架的外部让表达式在angular上下文内部执行。当手动处理事件,使用第三框架(比如jquery)或者调用setTimeout都可以使用apply()函数将值传递到angular应用中。
(1)不建议在控制器中使用$apply(),因为这样会导致难以测试。
(2)jquery和angular同时使用被视为一个肮脏的行为。