(十九)由一个bug想到的:使用jQuery和angularJS需要注意的事

本文探讨了AngularJS与jQuery结合使用时可能出现的DOM更新顺序问题。具体表现为,在页面加载时,子select控件无法正确获取父select的初始值,导致数据加载失败。通过分析发现,问题根源在于AngularJS的数据绑定是异步的,导致jQuery未能及时获取到更新后的DOM。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇文章源于今天在项目中定位的1个bug,情况是这样子的:任务大类和任务小类是2个存在级联关系的select,当任务大类变化的时候会级联刷新任务小类。

页面刚进来的时候,如果任务大类的默认选中值是空(place holder),任务小类select内容也是空(只有1个place holder),显示效果图如下,这样是没有问题的。



但是当页面刚进来的时候,任务大类不是空的时候,任务小类select数据没有加载,效果图如下,显然这是问题。



我们自定义的select控件,大致代码如下:

    Nf.mspl.form.dataField.SimpleSelect = function(id){
        	
	// 控件的配置信息
        var optionsJson = JSON.parse($.trim($E(id).attr("json")));
		  	
    	      	
      	// angularJS的controller,当页面bootstrap之后才会被调用
        this.ngController(id, function($scope, $element, $ionicModal) {
            "$scope:nomunge, $element:nomunge, $ionicModal:nomunge";
            self.$scope = $scope;
            $scope.listItems = [];
            
	    // 从服务端加载数据
            $scope.loadListFromService=function(parentValue){
				// start for loadData
            	Spl.MessageProcessor.loadData({
                    serviceId : optionsJson.serviceId,
                    data : {"parentValue" : parentValue},
                    success : function(json) {
                    	refreshScopeList(json);
                    },
                    error: function() {
                        console.error("invoke service["+optionsJson.serviceId+"] error.");
                    }
            	});
			}
			
			$scope.refreshScopeList = function(json){
				var tempForScope = [];
				for (var i = 0; i < json.length; i++)
				{
					tempForScope.push({"value":json[i].value, "text":json[i].text});
				}
					
				// refresh data in scope
				$scope.listItems = tempForScope;
			}
			
            
			
            // 没有父select 需要加载这个最顶级select的数据
            if(optionsJson.serviceId && !optionsJson.parentSelectName)
            {
            	$scope.loadListFromService("");
            }
            
            // init child select
            // this is a child select,but its parent select default value is not empty.Thus need to load child select.
            if(parentSelectName)
            {
            	var $parent_select = CurrentPage().getPageOutestJQuery().find("*[name='"+parentSelectName+"']");
				
		// 获取父select的默认选中的值
		var parentVal = $parent_select.val();
            
 	        console.log("begin to load child select.parentVal="+parentVal);
 	        if(parentVal)
 	        {
 	            $scope.loadListFromService(parentVal);
 	        }
            }
            
        });
    }

发现问题其实很简单,我们有一行日志"begin to load child select.parentVal",当任务大类默认选中值不是空的时候,这个时候任务小类也需要加载数据,但是在子select中获取到的父select的选中值是空。问题就出现在这里:初始化子select(任务小类)的时候,无法获取到父select(任务大类)的选中值。


有2种可能会导致上面的问题:1.获取到的$parent_select是错误的或者是没有获取到   2.$parent_select是正确的,但是默认选中值不对。只需要使用$parent_select.prop("outerHTML") 打印出html即可。最终结果大致如下:

<select id="nf3" name="task_category">
	<!-- ngRepeat: item in listItems --> 
</select>
到这里问题很明显了:虽然任务大类选择框最终显示是正确的,但是我们给$scope.listItems赋值之后,拿不到select下的<option>(这些option是通过angularJS的ng-repeat指令动态生成的)。可以这么理解:angularJS数据双向绑定是异步的,需要时间的。我们在给$scope中变量赋值之后,angular刷新界面dom是需要时间的。


下面这段代码模拟上面的效果:

<html>
<head>
<script src="jquery-1.11.1.min.js"></script>
<script src="angular.js"></script>
<script>

var rootMoudle = angular.module('module', []); 
rootMoudle.controller("root_controller",function($scope){
	// 设置select默认选中的option
	$scope.selectVal = 2;
	
	// 加载数据,生成option
	$scope.listItems = load();

	setTimeout(function(){
		console.log("delay");
		showSelectContent();
	},200);

	console.log("immediately");
	showSelectContent();
	
}); 

function showSelectContent()
{
	console.log("content=" + $("#aty").html());
	console.log("value="+$("#aty").val());
}


$(function(){
	angular.bootstrap($("#root"),["module"]);
})


// 模拟通过ajax加载服务端返回的数据
function load()
{
	return [{"value":1,"text":"a1"},
	{"value":2,"text":"b1"},
	{"value":3,"text":"c1"}];
}

</script>
</head>
<body id="root" ng-controller="root_controller">
	<select id="aty">
		<option ng-repeat="item in listItems" ng-selected="item.value == selectVal" value="{{item.value}}">{{item.text}}</option> 
	</select>
</body> 
</html>


chrome下的输出结果如下:



上面的输出结果的确可以证实我们的猜想:我们给$scope.listItems赋值之后,angularJS的双向绑定特性会将变化刷新到html页面上,但是之后的js代码是无法立刻获取到界面最新的dom,因为刷新dom是需要时间的,所以我们延时200ms之后就没有问题了。

可以看到同时使用angularJS和jQuery的时候容易出现这种顺序问题,jQuery的核心是操作界面上的dom,angularjs核心是操作scope中的数据。angularJS会根据scope中数据的变化,自动刷新dom(对dom进行增、删、改)。显然这种刷新是需要时间的,正是因为如此,才会导致jquery无法正确、及时的获取最新的dom。


当然,不要写出这些依赖于顺序的代码,如果我们使用了angularJS,就应该按照angularJS的理念去编写代码。网上有很多这种案例,按照传统jQuery的想法去编写angularJS会遇到各种问题或者困难。

可以看看“ Think in AngularJS:对比jQuery和AngularJS的不同思维模式”这篇文章。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值