在Angular指令中recursion

有几个受欢迎的recursionangular度指令Q&A在那里,这一切都归结为以下解决scheme之一:

  • 根据运行时作用域状态手动递增“编译”HTML
    • 示例1 [ stackoverflow ]
    • 例2 [ angular jsfiddles page ]
  • 根本不要使用指令,而是引用自身的<script>模板
    • 示例1 [ google groups ]

第一个问题是你不能删除以前编译的代码,除非你理解了手动编译过程。 第二种方法存在的问题是…不是一个指令,错过了强大的function,但更迫切的是,它不能像指令一样参数化; 它只是绑定到一个新的控制器实例。

我一直在手动做链接函数中的angular.bootstrap@compile() ,但是这给我留下了手动跟踪要移除和添加的元素的问题。

有没有一个好的方法来有一个参数化的recursion模式,pipe理添加/删除元素来反映运行时状态? 也就是说,一个带有添加/删除节点button的树以及一些其值被传递到节点的子节点的input字段。 也许第二种方法与链式范围的组合(但我不知道如何做到这一点)?

受@ dnc253提到的线程描述的解决scheme的启发,我将recursionfunction抽象为一个服务 。

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

其中使用如下:

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

看到这个Plunker的演示。 我最喜欢这个解决scheme,因为:

  1. 你不需要一个特殊的指令,使你的html不那么干净。
  2. recursion逻辑被抽象为RecursionHelper服务,所以你保持你的指令干净。

手动添加元素并编译它们绝对是一个完美的方法。 如果您使用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); } } }); 

我不确定这个解决scheme是否在你链接的例子或相同的基本概念中find,但是我需要一个recursion指令,并且我find了一个好的,简单的解决scheme 。

 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() { } } }; });​ 

您应该创buildrecursive指令,然后将其包装在进行recursion调用的元素周围。

从1.5.x开始,不需要更多的技巧,以下已经成为可能。 不再需要肮脏的工作!

这个发现是我寻找一个更好/更干净的recursion指令解决scheme的产物。 你可以在这里findhttps://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' }] }] }] }] }]; } 
 <script src="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> 

一段时间后使用几个解决方法,我一再回到这个问题。

我不满意的服务解决scheme,因为它适用于可以注入服务,但不适用于匿名模板片段的指令。

类似地,通过在指令中进行DOM操作而依赖于特定模板结构的解决scheme太具体而脆弱。

我有我认为是一个通用的解决scheme,将recursion封装为自己的指令,最低限度地干扰任何其他指令,并可以匿名使用。

下面是一个演示,你也可以玩plnkr: http ://plnkr.co/edit/MSiwnDFD81HAOXWvQWIM

 var hCollapseDirective = function () { return { link: function (scope, elem, attrs, ctrl) { scope.collapsed = false; scope.$watch('collapse', function (collapsed) { elem.toggleClass('collapse', !!collapsed); }); }, scope: {}, templateUrl: 'collapse.html', transclude: true } } var hRecursiveDirective = function ($compile) { return { link: function (scope, elem, attrs, ctrl) { ctrl.transclude(scope, function (content) { elem.after(content); }); }, controller: function ($element, $transclude) { var parent = $element.parent().controller('hRecursive'); this.transclude = angular.isObject(parent) ? parent.transclude : $transclude; }, priority: 500, // ngInclude < hRecursive < ngIf < ngRepeat < ngSwitch require: 'hRecursive', terminal: true, transclude: 'element', $$tlb: true // Hack: allow multiple transclusion (ngRepeat and ngIf) } } angular.module('h', []) .directive('hCollapse', hCollapseDirective) .directive('hRecursive', hRecursiveDirective) 
 /* Demo CSS */ * { box-sizing: border-box } html { line-height: 1.4em } .task h4, .task h5 { margin: 0 } .task { background-color: white } .task.collapse { max-height: 1.4em; overflow: hidden; } .task.collapse h4::after { content: '...'; } .task-list { padding: 0; list-style: none; } /* Collapse directive */ .h-collapse-expander { background: inherit; position: absolute; left: .5px; padding: 0 .2em; } .h-collapse-expander::before { content: '•'; } .h-collapse-item { border-left: 1px dotted black; padding-left: .5em; } .h-collapse-wrapper { background: inherit; padding-left: .5em; position: relative; } 
 <!DOCTYPE html> <html> <head> <link href="collapse.css" rel="stylesheet" /> <link href="style.css" rel="stylesheet" /> <script data-require="angular.js@1.3.15" data-semver="1.3.15" src="https://code.angularjs.org/1.3.15/angular.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js" data-semver="2.1.1" data-require="jquery@*"></script> <script src="script.js"></script> <script> function AppController($scope) { $scope.toggleCollapsed = function ($event) { $event.preventDefault(); $event.stopPropagation(); this.collapsed = !this.collapsed; } $scope.task = { name: 'All tasks', assignees: ['Citizens'], children: [ { name: 'Gardening', assignees: ['Gardeners', 'Horticulture Students'], children: [ { name: 'Pull weeds', assignees: ['Weeding Sub-committee'] } ], }, { name: 'Cleaning', assignees: ['Cleaners', 'Guests'] } ] } } angular.module('app', ['h']) .controller('AppController', AppController) </script> </head> <body ng-app="app" ng-controller="AppController"> <h1>Task Application</h1> <p>This is an AngularJS application that demonstrates a generalized recursive templating directive. Use it to quickly produce recursive structures in templates.</p> <p>The recursive directive was developed in order to avoid the need for recursive structures to be given their own templates and be explicitly self-referential, as would be required with ngInclude. Owing to its high priority, it should also be possible to use it for recursive directives (directives that have templates which include the directive) that would otherwise send the compiler into infinite recursion.</p> <p>The directive can be used alongside ng-if and ng-repeat to create recursive structures without the need for additional container elements.</p> <p>Since the directive does not request a scope (either isolated or not) it should not impair reasoning about scope visibility, which continues to behave as the template suggests.</p> <p>Try playing around with the demonstration, below, where the input at the top provides a way to modify a scope attribute. Observe how the value is visible at all levels.</p> <p>The collapse directive is included to further demonstrate that the recursion can co-exist with other transclusions (not just ngIf, et al) and that sibling directives are included on the recursive due to the recursion using whole 'element' transclusion.</p> <label for="volunteer">Citizen name:</label> <input id="volunteer" ng-model="you" placeholder="your name"> <h2>Tasks</h2> <ul class="task-list"> <li class="task" h-collapse h-recursive> <h4>{{task.name}}</h4> <h5>Volunteers</h5> <ul> <li ng-repeat="who in task.assignees">{{who}}</li> <li>{{you}} (you)</li> </ul> <ul class="task-list"> <li h-recursive ng-repeat="task in task.children"></li> </ul> <li> </ul> <script type="text/ng-template" id="collapse.html"> <div class="h-collapse-wrapper"> <a class="h-collapse-expander" href="#" ng-click="collapse = !collapse"></a> <div class="h-collapse-item" ng-transclude></div> </div> </script> </body> </html> 

有一个非常简单的解决方法,根本不需要指令。

那么,从这个意义上讲,如果你认为你需要指令的话,也许它甚至不是原始问题的解决scheme,但是如果你想要一个具有GUI的参数化子结构的recursionGUI结构,那么这是一个解决scheme。 这可能是你想要的。

该解决scheme基于使用ng-controller,ng-init和ng-include。 只要这样做,假设你的控制器被称为“MyController”,你的模板位于myTemplate.html中,并且你的控制器上有一个名为init的初始化函数,它带有参数A,B和C,参数化您的控制器。 那么解决办法如下:

myTemplate.htlm:

 <div> <div>Hello</div> <div ng-if="some-condition" ng-controller="Controller" ng-init="init(A, B, C)"> <div ng-include="'myTemplate.html'"></div> </div> </div> 

我明白地发现,这种结构可以像普通的香草angular那样recursion。 只要按照这个devise模式,你可以使用recursion的UI结构,而不需要任何高级的编译修补等。

在你的控制器里面

 $scope.init = function(A, B, C) { // Do something with A, B, C $scope.D = A + B; // D can be passed on to other controllers in myTemplate.html } 

我能看到的唯一缺点就是你必须忍受的笨重的语法。

现在Angular 2.0已经不在预览版了,我认为可以将Angular 2.0替代scheme添加到组合中。 至less以后会让人受益:

关键的概念是build立一个自引用的recursion模板:

 <ul> <li *for="#dir of directories"> <span><input type="checkbox" [checked]="dir.checked" (click)="dir.check()" /></span> <span (click)="dir.toggle()">{{ dir.name }}</span> <div *if="dir.expanded"> <ul *for="#file of dir.files"> {{file}} </ul> <tree-view [directories]="dir.directories"></tree-view> </div> </li> </ul> 

然后,将树对象绑定到模板,并观察recursion处理其余部分。 这里是一个完整的例子: http : //www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0

你可以使用angularrecursion注入器: https : //github.com/knyga/angular-recursion-injector

允许你做无限的深度嵌套与条件。 仅在需要时重新编译,只编译正确的元素。 代码没有魔力。

 <div class="node"> <span>{{name}}</span> <node--recursion recursion-if="subNode" ng-model="subNode"></node--recursion> </div> 

其中一个让它工作得更快,更简单的方法就是“–recursion”后缀。

我最终创build了一套recursion的基本指令。

国际海事组织这是比这里find的解决scheme更基础,如果不是更多,就像灵活,所以我们不一定使用UL / LI结构等…但显然这些是有道理的使用,但指令是不知道这个事实…

一个超级简单的例子是:

 <ul dx-start-with="rootNode"> <li ng-repeat="node in $dxPrior.nodes"> {{ node.name }} <ul dx-connect="node"/> </li> </ul> 

“dx-start-with”和“dx-connect”的实现可以在https://github.com/dotJEM/angular-tree

这意味着如果您需要8种不同的布局,则不必创build8个指令。

要创build一个树形视图,在那里你可以添加或删除节点,这将是相当简单的。 如: http : //codepen.io/anon/pen/BjXGbY?editors=1010

 angular .module('demo', ['dotjem.angular.tree']) .controller('AppController', function($window) { this.rootNode = { name: 'root node', children: [{ name: 'child' }] }; this.addNode = function(parent) { var name = $window.prompt("Node name: ", "node name here"); parent.children = parent.children || []; parent.children.push({ name: name }); } this.removeNode = function(parent, child) { var index = parent.children.indexOf(child); if (index > -1) { parent.children.splice(index, 1); } } }); 
 <div ng-app="demo" ng-controller="AppController as app"> HELLO TREE <ul dx-start-with="app.rootNode"> <li><button ng-click="app.addNode($dxPrior)">Add</button></li> <li ng-repeat="node in $dxPrior.children"> {{ node.name }} <button ng-click="app.removeNode($dxPrior, node)">Remove</button> <ul dx-connect="node" /> </li> </ul> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script> <script src="https://rawgit.com/dotJEM/angular-tree-bower/master/dotjem-angular-tree.min.js"></script> </div>