Angular指令中的递归

Ben*_*ema 175 javascript recursion angularjs

有一些流行的递归角度指令Q&A,它们都归结为以下解决方案之一:

第一个问题是,除非您可以理解地管理手动编译过程,否则无法删除以前编译的代码.第二种方法存在的问题是......不是指令而忽略了它的强大功能,但更迫切的是,它不能像指令一样参数化; 它只是绑定到一个新的控制器实例.

我一直在玩手动做一个angular.bootstrap@compile()在链接功能,但这让我有手动跟踪要删除和添加的元素的问题.

有一种很好的方法可以使用参数化递归模式来管理添加/删除元素以反映运行时状态吗?也就是说,一个带有添加/删除节点按钮的树和一些输入字段,其值从节点的子节点传递下来.也许是第二种方法与链式范围的组合(但我不知道如何做到这一点)?

Mar*_*ijk 313

受@ dnc253提到的线程中描述的解决方案的启发,我将递归功能抽象为服务.

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);
                    }
                }
            };
        }
    };
}]);
Run Code Online (Sandbox Code Playgroud)

使用如下:

module.directive("tree", ["RecursionHelper", function(RecursionHelper) {
    return {
        restrict: "E",
        scope: {family: '='},
        template: 
            '<p>{{ family.name }}</p>'+
            '<ul>' + 
                '<li ng-repeat="child in family.children">' + 
                    '<tree family="child"></tree>' +
                '</li>' +
            '</ul>',
        compile: function(element) {
            // Use the compile function from the RecursionHelper,
            // And return the linking function(s) which it returns
            return RecursionHelper.compile(element);
        }
    };
}]);
Run Code Online (Sandbox Code Playgroud)

有关演示,请参阅此Plunker.我最喜欢这个解决方案,因为:

  1. 你不需要一个特殊的指令,使你的HTML不那么干净.
  2. 递归逻辑被抽象到RecursionHelper服务中,因此您可以保持指令清洁.

更新:从Angular 1.5.x开始,不再需要技巧,但仅适用于模板,而不适用于templateUrl

  • 在您的示例中,您可以替换`compile:function(element){return RecursionHelper.compile(element); 使用`compile:RecursionHelper.compile`. (15认同)
  • 最初的问题是,当你使用递归指令时,AngularJS会进入无限循环.此代码通过在指令的compile事件期间删​​除内容,并在指令的link事件中编译和重新添加内容来中断此循环. (6认同)
  • 谢谢,很棒的解决方案 非常干净,开箱即用,让我在两个包含彼此工作的指令之间进行递归. (3认同)
  • 这是优雅的,因为如果/当Angular核心实现类似的支持,你可以删除自定义编译包装器,所有剩余的代码将保持不变. (2认同)

Sun*_*hah 23

手动添加元素并编译它们绝对是一种完美的方法.如果使用ng-repeat,则无需手动删除元素.

演示:http://jsfiddle.net/KNM4q/113/

.directive('tree', function ($compile) {
return {
    restrict: 'E',
    terminal: true,
    scope: { val: '=', parentData:'=' },
    link: function (scope, element, attrs) {
        var template = '<span>{{val.text}}</span>';
        template += '<button ng-click="deleteMe()" ng-show="val.text">delete</button>';

        if (angular.isArray(scope.val.items)) {
            template += '<ul class="indent"><li ng-repeat="item in val.items"><tree val="item" parent-data="val.items"></tree></li></ul>';
        }
        scope.deleteMe = function(index) {
            if(scope.parentData) {
                var itemIndex = scope.parentData.indexOf(scope.val);
                scope.parentData.splice(itemIndex,1);
            }
            scope.val = {};
        };
        var newElement = angular.element(template);
        $compile(newElement)(scope);
        element.replaceWith(newElement);
    }
}
});
Run Code Online (Sandbox Code Playgroud)


dnc*_*253 12

我不确定这个解决方案是否在您链接的示例或相同的基本概念中找到,但我需要一个递归指令,我找到了一个很好的,简单的解决方案.

module.directive("recursive", function($compile) {
    return {
        restrict: "EACM",
        priority: 100000,
        compile: function(tElement, tAttr) {
            var contents = tElement.contents().remove();
            var compiledContents;
            return function(scope, iElement, iAttr) {
                if(!compiledContents) {
                    compiledContents = $compile(contents);
                }
                iElement.append(
                    compiledContents(scope, 
                                     function(clone) {
                                         return clone; }));
            };
        }
    };
});

module.directive("tree", function() {
    return {
        scope: {tree: '='},
        template: '<p>{{ tree.text }}</p><ul><li ng-repeat="child in tree.children"><recursive><span tree="child"></span></recursive></li></ul>',
        compile: function() {
            return  function() {
            }
        }
    };
});?
Run Code Online (Sandbox Code Playgroud)

您应该创建该recursive指令,然后将其包装在进行递归调用的元素周围.

  • @Jack感谢您指出这一点.只花几个小时来解决这个问题,你的评论指出我正确的方向.对于使用捆绑服务的ASP.NET用户,请确保在捆绑时使用通配符包含时,目录中没有旧文件的缩小版本. (2认同)

jkr*_*ris 10

从Angular 1.5.x开始,不再需要花样,以下内容已经成为可能.不再需要肮脏的工作了!

这个发现是我寻找更好/更清晰的递归指令解决方案的副产品.你可以在这里找到它https://jsfiddle.net/cattails27/5j5au76c/.它支持到1.3.x.

angular.element(document).ready(function() {
  angular.module('mainApp', [])
    .controller('mainCtrl', mainCtrl)
    .directive('recurv', recurveDirective);

  angular.bootstrap(document, ['mainApp']);

  function recurveDirective() {
    return {
      template: '<ul><li ng-repeat="t in tree">{{t.sub}}<recurv tree="t.children"></recurv></li></ul>',
      scope: {
        tree: '='
      },
    }
  }

});

  function mainCtrl() {
    this.tree = [{
      title: '1',
      sub: 'coffee',
      children: [{
        title: '2.1',
        sub: 'mocha'
      }, {
        title: '2.2',
        sub: 'latte',
        children: [{
          title: '2.2.1',
          sub: 'iced latte'
        }]
      }, {
        title: '2.3',
        sub: 'expresso'
      }, ]
    }, {
      title: '2',
      sub: 'milk'
    }, {
      title: '3',
      sub: 'tea',
      children: [{
        title: '3.1',
        sub: 'green tea',
        children: [{
          title: '3.1.1',
          sub: 'green coffee',
          children: [{
            title: '3.1.1.1',
            sub: 'green milk',
            children: [{
              title: '3.1.1.1.1',
              sub: 'black tea'
            }]
          }]
        }]
      }]
    }];
  }
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<div>
  <div ng-controller="mainCtrl as vm">
    <recurv tree="vm.tree"></recurv>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你。你能把我链接到介绍这个功能的更新日志吗?谢谢! (2认同)