半夜三更睡不着,记录一下昨天工作遇到的问题。
出于项目的需要,写了一个递归的指令。这东西居然导致chrome假死。看起来好严重,连强制退出都不行了,害我重启电脑。
简单介绍一下背景:
首先是有一个树状的数据结构,大约是这样子:
{
id:xx,
children:{
id:yy,
children:{...}
}
}
<div>
<label>{{node.id}}</label>
<div ng-if="node.children.length>0">
<ul>
<li ng-repeat="child in node.children">
<node-directive node="child"></node-directive>
</li>
</ul>
</div>
</div>
angular.module('module', []).directive('nodeDirective', ..., function() {
return {
restrict:'E',
scope:{node:'='},
link:function(scope,elem,attrs) {
// do something.
}
}
});
这时猜测是angular编译递归指令的bug。简单验证一下:把递归的html代码注释掉,再刷新浏览器页面,果然正常显示!居然能出这种错误,先鄙视一下AngularJS。
毕竟我是用angular的初哥,遇到这种问题,只能先求助谷歌,找到两个解决方案:
1. http://stackoverflow.com/questions/14430655/recursion-in-angular-directives
这个方案在伟大的stackoverflow中搜到;具体步骤就是:
增加一个recursionHelper.js先:
module.factory('RecursionHelper', ['$compile', function($compile){
return {
/**
* Manually compiles the element, fixing the recursion loop.
* @param element
* @param [link] A post-link function, or an object with function(s) registered via pre and post properties.
* @returns An object containing the linking functions.
*/
compile: function(element, link){
// Normalize the link parameter
if(angular.isFunction(link)){
link = { post: link };
}
// Break the recursion loop by removing the contents
var contents = element.contents().remove();
var compiledContents;
return {
pre: (link && link.pre) ? link.pre : null,
/**
* Compiles and re-adds the contents
*/
post: function(scope, element){
// Compile the contents
if(!compiledContents){
compiledContents = $compile(contents);
}
// Re-add the compiled contents to the element
compiledContents(scope, function(clone){
element.append(clone);
});
// Call the post-linking function, if any
if(link && link.post){
link.post.apply(null, arguments);
}
}
};
}
};
}]);
然后给原来的指令增加compile方法,把之前的link方法作为参数传入:
angular.module('module', []).directive('nodeDirective', ..., function() {
return {
restrict:'E',
scope:{node:'='},
compile : function(element) {
return recursionHelper.compile(element,function(scope, elem, attr) {
// do something.
});
}
}
});
2. http://sporto.github.io/blog/2013/06/24/nested-recursive-directives-in-angular/
这个方案的思路是不使用递归指令,然后在link方法修改当前元素,增加孩子结点(这个结点就是之前递归使用的本指令)。可以说是曲线救国了。
模版修改为:
<div>
<label>{{node.id}}</label>
</div>
指令修改为:
angular.module('module', []).directive('', ..., function() {
return {
restrict:'E',
scope:{node:'='},
link:function(scope,elem,attrs) {
if (angular.isArray(scope.node.children)) {
elem.append("\
<ul> \
<li ng-repeat="child in node.children">\
<node-directive node="child"></node-directive>\
</li>\
</ul>\
");
$compile(elem.contents())(scope)
}
// do something.
}
}
});
个人比较喜欢第一个方案。第二个方案,是通过代码来修改dom了,这样做不优雅,应该尽量避免。
我是用第一个方案解决问题了,但是一个下午已经过去了。