【问题标题】:Is it possible to make a Tree View with Angular?是否可以使用 Angular 创建树视图?
【发布时间】:2012-08-07 21:38:35
【问题描述】:

我希望在网络应用程序中以树状结构显示数据。我希望使用 Angular 来完成这项任务。

看起来 ng-repeat 将允许我遍历节点列表,但是当给定节点的深度增加时,我该如何进行嵌套?

我尝试了following code,但 HTML 的自动转义阻止了它的工作。另外,最后的ul标签放错地方了。

我很确定我解决这个问题的方式完全错误。

有什么想法吗?

【问题讨论】:

标签: angularjs


【解决方案1】:

看看这个小提琴

原文:http://jsfiddle.net/brendanowen/uXbn6/8/

更新:http://jsfiddle.net/animaxf/uXbn6/4779/

这应该让您很好地了解如何使用角度显示tree like structure。这有点像在 html 中使用递归!

【讨论】:

  • 为什么不声明your source?你在那个帖子里写了一篇文章,现在你在这里发布一个带有你自己名字的网址?
  • 这是一个相同的版本(我认为),除了加载速度更快(至少对我而言),因为它没有在 CSS 部分中内联 Twitter Bootstrap。 jsfiddle.net/brendanowen/uXbn6/8
  • 老兄,你应该说明你的来源。
  • 我真的厌倦了人们不断评论这个 URL 中有我的名字(因此它是抄袭!)。不幸的是,这就是 jsfiddle 的工作原理。如果您在登录时分叉某些东西,它会保留您的用户名。话虽如此,我现在已经链接到原始 URL。如果答案错误,请否决答案 - 在这种情况下,答案恰好是正确的,我拥有的备份 URL 似乎包含我的名字。
  • 我刚刚在您的版本中添加了折叠和展开按钮:jsfiddle.net/uXbn6/639
【解决方案2】:

如果您使用的是 Bootstrap CSS...

我基于引导程序“导航”列表为 AngularJS 创建了一个简单的可重复使用的树控件(指令)。我添加了额外的缩进、图标和动画。 HTML 属性用于配置。

它不使用递归。

我称它为 angular-bootstrap-nav-tree(这个名字很吸引人,你不觉得吗?)

有一个例子here,来源是here

【讨论】:

  • 它很漂亮,但要注意它不适用于 Angular 1.0.x 分支。
  • 是的,它使用了新的动画东西...需要 Angular 1.1.5(我认为?)
  • 更新:它现在适用于 Angular 1.1.5 或 Angular 1.2.0,也适用于 Bootsrap 2 或 Bootstrap 3
  • 仅供参考,如果使用 Bower,Nick 现在可以轻松安装 - “bower search angular-bootstrap-nav-tree”和“bower install angular-bootstrap-nav-tree -保存”,你就完成了。
  • @Nick Perkins - 请您解释一下为什么您的 angular-bootstrap-nav-tree 没有用于删除分支/节点的 API。至少,通过对源代码的快速检查,并检查您的测试/示例,似乎没有那个选项。这肯定是一个严重的遗漏吗?
【解决方案3】:

当做这样的事情时,最好的解决方案是递归指令。然而,当你做出这样的指令时,你会发现 AngularJS 进入了一个无限循环。

解决办法是让指令在编译事件中移除元素,然后在链接事件中手动编译添加。

我在this thread 中发现了这一点,并抽象了这个功能into a service

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", 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) {
            return RecursionHelper.compile(element);
        }
    };
});

请参阅此Plunker 以获取演示。 我最喜欢这个解决方案,因为:

  1. 您不需要使 html 变得不那么干净的特殊指令。
  2. 递归逻辑被抽象到 RecursionHelper 服务中,因此您可以保持指令干净。

更新:添加了对自定义链接功能的支持。

【讨论】:

  • 这看起来非常简洁和强大,知道为什么这不是 angularjs 中的默认行为吗?
  • 当像这样使用“编译”时,如何为范围添加额外的属性?一旦“编译”存在,“链接”功能似乎不再可用......
  • @bkent314 我添加了对此的支持。它现在以与 compile 可以返回它们相同的方式接受链接函数。我还为该服务创建了一个 Github 项目。
  • @MarkLagendijk 非常非常漂亮!从指令中抽象出递归,你应该得到很多支持。我见过的所有指令看起来都非常复杂,混入了这种逻辑。有没有办法让你的 RecursionHelper 与嵌入一起工作?
  • 我真的建议你在这种类型的解决方案中投入一些数据 - 是的,几乎每个人都使用递归指令实现树,这很容易。但它与 ng-repeat $digest 一样非常慢 - 一旦你到达数百个节点,它就不会执行。
【解决方案4】:

angular-ui-tree 似乎对我来说做得很好

【讨论】:

    【解决方案5】:

    这里是一个使用递归指令的例子:http://jsfiddle.net/n8dPm/ 取自https://groups.google.com/forum/#!topic/angular/vswXTes_FtM

    module.directive("tree", function($compile) {
    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(tElement, tAttr) {
            var contents = tElement.contents().remove();
            var compiledContents;
            return function(scope, iElement, iAttr) {
                if(!compiledContents) {
                    compiledContents = $compile(contents);
                }
                compiledContents(scope, function(clone, scope) {
                         iElement.append(clone); 
                });
            };
        }
    };
    });
    

    【讨论】:

    • 我正在试验这个,我也想使用嵌入,你认为这可能吗?
    【解决方案6】:

    这个好像更完整一点:https://github.com/dump247/angular.tree

    【讨论】:

      【解决方案7】:

      另一个基于original source 的示例,已经有一个示例树结构(更容易了解 IMO 的工作原理)和一个用于搜索树的过滤器:

      JSFiddle

      【讨论】:

        【解决方案8】:

        这么多很棒的解决方案,但我觉得它们都以某种方式使事情变得过于复杂。

        我想创建一些东西来重新创建@Mark Lagendijk 的 awnser 的简单性,但没有在指令中定义模板,而是让“用户”在 HTML 中创建模板...

        https://github.com/stackfull/angular-tree-repeat 等获得的想法......我最终创建了这个项目:https://github.com/dotJEM/angular-tree

        这可以让你像这样构建你的树:

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

        对我来说,这比必须为不同结构的树创建多个指令更干净......本质上称上面的树有点错误,它从@ganaraj 的“递归模板”的 awnser 中挑选出更多,但是允许我们在需要树的地方定义模板。

        (你可以使用基于脚本标签的模板来做到这一点,但它仍然必须位于实际的树节点之外,而且感觉还是有点糟糕......)

        留在这里只是为了另一种选择......

        【讨论】:

        • 更新:从 1.5 开始,Angular 现在在某种程度上原生支持递归指令。这大大缩小了 dotjem/angular-tree 的用例范围。
        【解决方案9】:

        您可以尝试使用带有 Angular-Ui-Tree 的 Angular-Tree-DnD 示例,但我已编辑,与表格、网格、列表兼容。

        • 能够拖放
        • 列表的扩展函数指令(next, prev, getChildren,...)
        • 过滤数据。
        • OrderBy(版本)

        【讨论】:

        • 谢谢。我需要拖放,这似乎是唯一的解决方案!
        【解决方案10】:

        基于@ganaraj 的answer 和@dnc253 的answer,我只是为具有选择、添加、删除和编辑功能的树结构做了一个简单的“指令”。

        Jsfiddle:http://jsfiddle.net/yoshiokatsuneo/9dzsms7y/

        HTML:

        <script type="text/ng-template" id="tree_item_renderer.html">
            <div class="node"  ng-class="{selected: data.selected}" ng-click="select(data)">
                <span ng-click="data.hide=!data.hide" style="display:inline-block; width:10px;">
                    <span ng-show="data.hide && data.nodes.length > 0" class="fa fa-caret-right">+</span>
                    <span ng-show="!data.hide && data.nodes.length > 0" class="fa fa-caret-down">-</span>
                </span>
                <span ng-show="!data.editting" ng-dblclick="edit($event)" >{{data.name}}</span>
                <span ng-show="data.editting"><input ng-model="data.name" ng-blur="unedit()" ng-focus="f()"></input></span>
                <button ng-click="add(data)">Add node</button>
                <button ng-click="delete(data)" ng-show="data.parent">Delete node</button>
            </div>
            <ul ng-show="!data.hide" style="list-style-type: none; padding-left: 15px">
                <li ng-repeat="data in data.nodes">
                    <recursive><sub-tree data="data"></sub-tree></recursive>
                </li>
            </ul>
        </script>
        <ul ng-app="Application" style="list-style-type: none; padding-left: 0">
            <tree data='{name: "Node", nodes: [],show:true}'></tree>
        </ul>
        

        JavaScript:

        angular.module("myApp",[]);
        
        /* https://stackoverflow.com/a/14657310/1309218 */
        angular.module("myApp").
        directive("recursive", function($compile) {
            return {
                restrict: "EACM",
                require: '^tree',
                priority: 100000,
        
                compile: function(tElement, tAttr) {
                    var contents = tElement.contents().remove();
                    var compiledContents;
                    return function(scope, iElement, iAttr) {
                        if(!compiledContents) {
                            compiledContents = $compile(contents);
                        }
                        compiledContents(scope, 
                                             function(clone) {
                                 iElement.append(clone);
                                                 });
                    };
                }
            };
        });
        
        angular.module("myApp").
        directive("subTree", function($timeout) {
            return {
                restrict: 'EA',
                require: '^tree',
                templateUrl: 'tree_item_renderer.html',
                scope: {
                    data: '=',
                },
                link: function(scope, element, attrs, treeCtrl) {
                    scope.select = function(){
                        treeCtrl.select(scope.data);
                    };
                    scope.delete = function() {
                        scope.data.parent.nodes.splice(scope.data.parent.nodes.indexOf(scope.data), 1);
                    };
                    scope.add = function() {
                        var post = scope.data.nodes.length + 1;
                        var newName = scope.data.name + '-' + post;
                        scope.data.nodes.push({name: newName,nodes: [],show:true, parent: scope.data});
                    };
                    scope.edit = function(event){
                        scope.data.editting = true;
                        $timeout(function(){event.target.parentNode.querySelector('input').focus();});
                    };
                    scope.unedit = function(){
                        scope.data.editting = false;
                    };
        
                }
            };
        });
        
        
        angular.module("myApp").
        directive("tree", function(){
            return {
                restrict: 'EA',
                template: '<sub-tree data="data" root="data"></sub-tree>',
                controller: function($scope){
                    this.select = function(data){
                        if($scope.selected){
                            $scope.selected.selected = false;
                        }
                        data.selected = true;
                        $scope.selected = data;
                    };
                },
                scope: {
                    data: '=',
                }
            }
        });
        

        【讨论】:

          【解决方案11】:

          是的,这绝对是可能的。这里的问题可能假设 Angular 1.x,但为了将来参考,我包括一个 Angular 2 示例:

          从概念上讲,您所要做的就是创建一个递归模板:

          <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>
          

          然后你将一个树对象绑定到模板,让 Angular 发挥它的魔力。这个概念显然也适用于 Angular 1.x。

          这是一个完整的例子:http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0

          【讨论】:

            【解决方案12】:

            您可以为此使用 angular-recursion-injector: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”后缀。

            【讨论】:

              【解决方案13】:

              当树结构很大时,Angular(最高 1.4.x)在渲染递归模板时会变得非常慢。在尝试了这些建议之后,我最终创建了一个简单的 HTML 字符串并使用ng-bind-html 来显示它。当然,这不是使用 Angular 特性的方式

              这里显示了一个简单的递归函数(使用最少的 HTML):

              function menu_tree(menu, prefix) {
                  var html = '<div>' + prefix + menu.menu_name + ' - ' + menu.menu_desc + '</div>\n';
                  if (!menu.items) return html;
                  prefix += menu.menu_name + '/';
                  for (var i=0; i<menu.items.length; ++i) {
                      var item = menu.items[i];
                      html += menu_tree(item, prefix);
                  }
                  return html;
              }
              // Generate the tree view and tell Angular to trust this HTML
              $scope.html_menu = $sce.trustAsHtml(menu_tree(menu, ''));
              

              在模板中,只需要这一行:

              <div ng-bind-html="html_menu"></div>
              

              这绕过了 Angular 的所有数据绑定,只用递归模板方法的一小部分时间显示 HTML。

              使用这样的菜单结构(Linux 文件系统的部分文件树):

              menu = {menu_name: '', menu_desc: 'root', items: [
                          {menu_name: 'bin', menu_desc: 'Essential command binaries', items: [
                              {menu_name: 'arch', menu_desc: 'print machine architecture'},
                              {menu_name: 'bash', menu_desc: 'GNU Bourne-Again SHell'},
                              {menu_name: 'cat', menu_desc: 'concatenate and print files'},
                              {menu_name: 'date', menu_desc: 'display or set date and time'},
                              {menu_name: '...', menu_desc: 'other files'}
                          ]},
                          {menu_name: 'boot', menu_desc: 'Static files of the boot loader'},
                          {menu_name: 'dev', menu_desc: 'Device files'},
                          {menu_name: 'etc', menu_desc: 'Host-specific system configuration'},
                          {menu_name: 'lib', menu_desc: 'Essential shared libraries and kernel modules'},
                          {menu_name: 'media', menu_desc: 'Mount point for removable media'},
                          {menu_name: 'mnt', menu_desc: 'Mount point for mounting a filesystem temporarily'},
                          {menu_name: 'opt', menu_desc: 'Add-on application software packages'},
                          {menu_name: 'sbin', menu_desc: 'Essential system binaries'},
                          {menu_name: 'srv', menu_desc: 'Data for services provided by this system'},
                          {menu_name: 'tmp', menu_desc: 'Temporary files'},
                          {menu_name: 'usr', menu_desc: 'Secondary hierarchy', items: [
                              {menu_name: 'bin', menu_desc: 'user utilities and applications'},
                              {menu_name: 'include', menu_desc: ''},
                              {menu_name: 'local', menu_desc: '', items: [
                                  {menu_name: 'bin', menu_desc: 'local user binaries'},
                                  {menu_name: 'games', menu_desc: 'local user games'}
                              ]},
                              {menu_name: 'sbin', menu_desc: ''},
                              {menu_name: 'share', menu_desc: ''},
                              {menu_name: '...', menu_desc: 'other files'}
                          ]},
                          {menu_name: 'var', menu_desc: 'Variable data'}
                      ]
                     }
              

              输出变成:

              - root
              /bin - Essential command binaries
              /bin/arch - print machine architecture
              /bin/bash - GNU Bourne-Again SHell
              /bin/cat - concatenate and print files
              /bin/date - display or set date and time
              /bin/... - other files
              /boot - Static files of the boot loader
              /dev - Device files
              /etc - Host-specific system configuration
              /lib - Essential shared libraries and kernel modules
              /media - Mount point for removable media
              /mnt - Mount point for mounting a filesystem temporarily
              /opt - Add-on application software packages
              /sbin - Essential system binaries
              /srv - Data for services provided by this system
              /tmp - Temporary files
              /usr - Secondary hierarchy
              /usr/bin - user utilities and applications
              /usr/include -
              /usr/local -
              /usr/local/bin - local user binaries
              /usr/local/games - local user games
              /usr/sbin -
              /usr/share -
              /usr/... - other files
              /var - Variable data
              

              【讨论】:

                【解决方案14】:

                并不复杂。

                <div ng-app="Application" ng-controller="TreeController">
                    <table>
                        <thead>
                            <tr>
                                <th>col 1</th>
                                <th>col 2</th>
                                <th>col 3</th>
                            </tr>
                        </thead>
                        <tbody ng-repeat="item in tree">
                            <tr>
                                <td>{{item.id}}</td>
                                <td>{{item.fname}}</td>
                                <td>{{item.lname}}</td>
                            </tr>
                            <tr ng-repeat="children in item.child">
                                <td style="padding-left:15px;">{{children.id}}</td>
                                <td>{{children.fname}}</td>
                            </tr>
                        </tbody>
                     </table>
                </div>
                

                控制器代码:

                angular.module("myApp", []).
                controller("TreeController", ['$scope', function ($scope) {
                    $scope.tree = [{
                        id: 1,
                        fname: "tree",
                        child: [{
                            id: 1,
                            fname: "example"
                        }],
                        lname: "grid"
                    }];
                
                
                }]);
                

                【讨论】:

                  猜你喜欢
                  • 2012-07-26
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-04-03
                  • 2020-07-13
                  • 2019-08-23
                  • 1970-01-01
                  相关资源
                  最近更新 更多