解决Angular的递归指令问题

半夜三更睡不着,记录一下昨天工作遇到的问题。

出于项目的需要,写了一个递归的指令。这东西居然导致chrome假死。看起来好严重,连强制退出都不行了,害我重启电脑

简单介绍一下背景:

首先是有一个树状的数据结构,大约是这样子:

{
    id:xx,
    children:{
        id:yy,
        children:{...}
    }
}

想在页面显示这个数据,一般的思路就是写一个指令嘛,模版大约是这个样子的(
假设指令的名字就是nodeDirective):

<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.
        }
    }
});

看上去没有神马问题,下一步当然是刷新浏览器页面,然后。。。死了,chrome死了死了的。一开始还怀疑是chrome的问题,以为打开太多tab了;还怀疑过是Mac系统的问题,想着这不是windows,怎么也死机了。好吧,后来证实是我多疑了,冤枉了他俩。

这时猜测是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了,这样做不优雅,应该尽量避免。

我是用第一个方案解决问题了,但是一个下午已经过去了。

时间: 2024-10-15 18:37:30

解决Angular的递归指令问题的相关文章

Angular实现递归指令 - Tree View

在层次数据结构展示中,树是一种极其常见的展现方式.比如系统中目录结构.企业组织结构.电子商务产品分类都是常见的树形结构数据. 这里我们采用Angular的方式来实现这类常见的tree view结构. 首先我们定义数据结构,采用以children属性来挂接子节点方式来展现树层次结构,示例如下: [ { "id":"1", "pid":"0", "name":"家用电器", "ch

Angular 2 属性指令 vs 结构指令

Angular 2 的指令有以下三种: 组件(Component directive):用于构建UI组件,继承于 Directive 类 属性指令(Attribute directive):  用于改变组件的外观或行为 结构指令(Structural directive):  用于动态添加或删除DOM元素来改变DOM布局 组件 import { Component } from '@angular/core'; @Component({       selector: 'my-app', // 

angular的自定义指令---详解

1.angualr指令 在angualr自己里面有许多丰富的指令,但都是平时所常见的,但对于自己所需要的可能有所欠缺,所以自己可能会摒弃原声指令,自己封装更为健壮灵活的指令: 其实angular里面的指令也是基于下面所用到的步骤来创建的,只不过他都添加到了全局中,所以可以直接使用: 2.创建自定义指令 首先创建模块app,再使用app的服务directive方法, 创建指令内容第一个参数自定义的名字,第二个参数是自定义参数属性对象,该对象包括的属性基本在代码注释解释清楚: // a.创建模块 v

angular.fullpage.js指令的使用方法(详解)

接之前,jquery.fullpage在angular单页应用中存在重复初始化,分页器加倍问题,索性直接找到了angular.fullpage.js全屏滚动指令应用 angular-fullpage文档这里(将的不仔细自己做有点麻烦,不过有个demo) demo的却也不好看,直接上代码详解 1.首先得引用文件(当然angular.js必须的,省略) 2.导入angular.module内(fullPage.js放入,其他可以忽略自己用到的) 3. angular单页路由配置 state中加入(c

Angular内置指令(二)

目录: $rootScope,ng-app,.run(),ng-include,ng-repeat,ng-if,ng-switch,ng-init ng-show/ng-hide,ng-model,ng-bind-template,ng-change,ng-submit,ng-cloak,ng-bind 详细介绍: $rootScope 是作用域链的起始点,任何嵌套在ng-app内的指令都会继承$rootScope ng-app 任何具有ng-app属性的dom元素都将被标记为$rootScop

angular : direative : scope 指令scope和transclude的关系

今天记入的是指令的scope和transclude关系 a 和 b 都是指令 <div a> <div b></div> </div> a transclude了b,b的$$prevSibling是a,而a的$$prevSibling不是b <div a> <div ng-transclude=""> <div b></div> </div> </div> angul

Angular JS(二) 指令部分

还是打算分开来写,因为这部分正好打算写一下Angular JS里面的常用指令,篇幅会比较多,就不放在一起了. 接上部分继续说一下Angular JS的骚操作23333 (四)常用指令 使用这些指令可以省去频繁的DOM操作 1.ng-bind 作用与{{}}类似,但是用ng-bind比{{}}更好,可以解决{{}}闪烁问题 <body ng-app="app" ng-controller="appCtrl"><!-- 两者等价,只是在网络比较慢的时候

Angular JS - 6 - Angular JS 常用指令

1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 </head> 7 <!-- 8 1. Angular指令 9 * Angular为HTML页面扩展的: 自定义标签属性或标签 10 * 与Angular的作用域对象(scope)交互,扩展页面的动

angular中重要指令介绍($eval,$parse和$compile)

在angular的服务中,有一些服务你不得不去了解,因为他可以说是ng的核心,而今天,我要介绍的就是ng的两个核心服务,$parse和$compile.其实这两个服务讲的人已经很多了,但是100个读者就有100个哈姆雷特,我在这里讲讲自己对于他们两个服务的理解. 大家可能会疑问,$eval呢,其实他并不是一个服务,他是scope里面的一个方法,并不能算服务,而且它也基于parse的,所以只能算是$parse的另一种写法而已,我们看一下ng源码中$eval的定义是怎样的就知道了 $eval: fu