Có thể thực hiện Chế độ xem cây với Angular không?


177

Tôi đang tìm cách hiển thị dữ liệu trong cấu trúc cây trong ứng dụng web. Tôi đã hy vọng sử dụng Angular cho nhiệm vụ này.

Có vẻ như ng-repeat sẽ cho phép tôi lặp qua danh sách các nút, nhưng làm thế nào tôi có thể làm tổ khi độ sâu của một nút đã cho tăng?

Tôi đã thử đoạn mã sau , nhưng việc tự động thoát HTML đang ngăn điều này hoạt động. Thêm vào đó, thẻ ul kết thúc ở sai vị trí.

Tôi khá chắc chắn rằng tôi đang đi về vấn đề này hoàn toàn sai cách.

Có ý kiến ​​gì không?


Tôi vừa trả lời câu hỏi này theo một cách khá chung chung về một câu hỏi khác: stackoverflow.com/questions/14430655/ Lời
tilgovi

Câu trả lời:


231

Có một cái nhìn vào fiddle này

Bản gốc: http://jsfiddle.net/enamendanowen/uXbn6/8/

Đã cập nhật: http://jsfiddle.net/animaxf/uXbn6/4779/

Điều này sẽ cung cấp cho bạn một ý tưởng tốt về cách hiển thị tree like structurebằng cách sử dụng góc. Đây là loại sử dụng đệ quy trong html!


94
Tại sao không nêu rõ nguồn của bạn ? bạn đã viết một bài đăng trong chủ đề đó và bây giờ bạn đang đăng một url ở đây với tên riêng của bạn trong đó?
Janus Troelsen

5
Đây là một phiên bản giống hệt nhau (tôi nghĩ), ngoại trừ việc nó tải nhanh hơn nhiều (ít nhất là đối với tôi), vì nó không có Twitter Bootstrap được in trong phần CSS. jsfiddle.net/enamendanowen/uXbn6/8
KajMagnus

10
anh bạn nên nói rõ nguồn của bạn
Ajax3,14

46
Tôi thực sự mệt mỏi khi mọi người liên tục bình luận về điều này rằng URL có tên của tôi trong đó (và do đó nó là đạo văn!). Thật không may là cách jsfiddle hoạt động. Nếu bạn rẽ nhánh một cái gì đó trong khi bạn đăng nhập, nó sẽ giữ lại tên người dùng của bạn. Phải nói rằng tôi đã liên kết với URL ban đầu. Downvote một câu trả lời nếu nó sai - Câu trả lời xảy ra là chính xác trong kịch bản này với một điều mà URL dự phòng mà tôi có dường như chứa tên của tôi trong đó.
ganaraj

5
Tôi vừa thêm nút thu gọn và mở rộng vào phiên bản của bạn: jsfiddle.net/uXbn6/639
jbaylina

77

Nếu bạn đang sử dụng Bootstrap CSS ...

Tôi đã tạo một điều khiển cây có thể sử dụng lại đơn giản (chỉ thị) cho AngularJS dựa trên danh sách "nav" của Bootstrap. Tôi đã thêm thụt lề, biểu tượng và hình ảnh động. Các thuộc tính HTML được sử dụng để cấu hình.

Nó không sử dụng đệ quy.

Tôi gọi nó là angular-bootstrap-nav-tree (tên hấp dẫn, bạn không nghĩ sao?)

Có một ví dụ ở đây , và nguồn ở đây .


1
Nó rất đẹp, nhưng được cảnh báo là nó không hoạt động trên nhánh Angular 1.0.x.
Danita

3
Vâng, nó sử dụng công cụ hoạt hình mới ... yêu cầu Angular 1.1.5 (tôi nghĩ vậy?)
Nick Perkins

3
CẬP NHẬT: hiện tại nó hoạt động với Angular 1.1.5 hoặc Angular 1.2.0, và cũng hoạt động với Bootsrap 2 hoặc Bootstrap 3
Nick Perkins

1
Chỉ FYI, nếu sử dụng Bower, Nick đã sẵn sàng để cài đặt dễ dàng - "bower search angular-bootstrap-nav-tree" và "bower install angular-bootstrap-nav-tree --save" và bạn đã hoàn thành.
arcseldon

2
@Nick Perkins - vui lòng bạn có thể giải thích lý do tại sao angular-bootstrap-nav-tree của bạn không có API để xóa Branch / Node. Ít nhất, từ việc kiểm tra nhanh nguồn và kiểm tra bài kiểm tra / ví dụ của bạn dường như không có tùy chọn đó. Đây là một thiếu sót quan trọng, chắc chắn?
arcseldon

35

Khi thực hiện một cái gì đó như thế này, giải pháp tốt nhất là một chỉ thị đệ quy. Tuy nhiên, khi bạn thực hiện một chỉ thị như vậy, bạn phát hiện ra rằng AngularJS đi vào một vòng lặp vô tận.

Giải pháp cho việc này là để cho lệnh xóa phần tử trong sự kiện biên dịch, và biên dịch thủ công và thêm chúng vào các sự kiện liên kết.

Tôi đã tìm hiểu về điều này trong chủ đề này , và trừu tượng hóa chức năng này vào một dịch vụ .

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

Với dịch vụ này, bạn có thể dễ dàng thực hiện một lệnh cây (hoặc các chỉ thị đệ quy khác). Dưới đây là một ví dụ về một chỉ thị cây:

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

Xem Plunker này cho một bản demo. Tôi thích giải pháp này nhất vì:

  1. Bạn không cần một lệnh đặc biệt làm cho html của bạn không sạch sẽ.
  2. Logic đệ quy được trừu tượng hóa trong dịch vụ RecursionHelper, vì vậy bạn giữ cho các lệnh của mình sạch sẽ.

Cập nhật: Đã thêm hỗ trợ cho các chức năng liên kết tùy chỉnh.


1
điều này dường như rất gọn gàng và mạnh mẽ, có ai biết tại sao đây không phải là một hành vi mặc định trong angularjs không?
Paul

Khi sử dụng "biên dịch" như thế này, làm thế nào để thêm các thuộc tính bổ sung vào phạm vi? Chức năng "liên kết" dường như không còn khả dụng sau khi "biên dịch" ở đó ...
Brian Kent

1
@ bkent314 Tôi đã thêm hỗ trợ cho việc này. Bây giờ nó chấp nhận các hàm liên kết theo cùng một cách như biên dịch có thể trả về chúng. Tôi cũng đã tạo một dự án Github cho dịch vụ.
Mark Lagendijk

@MarkLagendijk Rất, rất lắt léo! Bạn xứng đáng nhận được nhiều sự ủng hộ cho việc trừu tượng hóa đệ quy ra khỏi chỉ thị. Tất cả các chỉ thị mà tôi thấy có vẻ phức tạp vô vọng với logic đó được trộn lẫn. Có cách nào để RecursionHelper của bạn hoạt động với loại trừ không?
acjay

Tôi thực sự khuyên bạn nên ném một số dữ liệu vào loại giải pháp này - vâng, hầu như mọi người đều thực hiện cây với các chỉ thị đệ quy, thật dễ dàng. Nhưng nó cực kỳ chậm khi ng-lặp lại $ digest - một khi bạn nhận được hàng trăm nút, điều này không thực hiện được.
Artemiy


15

Dưới đây là một ví dụ sử dụng chỉ thị đệ quy: http://jsfiddle.net/n8dPm/ Lấy từ 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); 
            });
        };
    }
};
});

tôi đã thử nghiệm điều này và tôi cũng muốn sử dụng loại trừ, bạn có nghĩ là nó có thể không?
L.Trabacchin 16/07/2015


5

Một ví dụ khác dựa trên nguồn ban đầu , với cấu trúc cây mẫu đã có sẵn (dễ dàng hơn để xem cách nó hoạt động IMO) và bộ lọc để tìm kiếm cây:

Câu đố


4

Rất nhiều giải pháp tuyệt vời, nhưng tôi cảm thấy tất cả chúng theo cách này hay cách khác làm phức tạp hóa mọi thứ một chút.

Tôi muốn tạo một cái gì đó tái tạo sự đơn giản của awMower của @Mark Lagendijk, nhưng không có nó xác định một mẫu trong chỉ thị, nhưng sẽ để "người dùng" tạo mẫu trong HTML ...

Với các ý tưởng được lấy từ https://github.com/stackfull/angular-tree-repeat, v.v ... Tôi đã kết thúc với việc tạo dự án: https://github.com/dotJEM/angular-tree

Cho phép bạn xây dựng cây của bạn như:

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

Đối với tôi sạch sẽ hơn là phải tạo nhiều chỉ thị cho các cây có cấu trúc khác nhau .... Về bản chất, việc gọi cây ở trên là hơi sai, nó chọn nhiều hơn từ "mẫu đệ quy" của @ ganaraj, nhưng cho phép chúng ta xác định mẫu nơi chúng ta cần cây.

(bạn có thể làm điều đó với một mẫu dựa trên thẻ script, nhưng nó vẫn phải ngồi ngay bên ngoài nút cây thực sự và nó vẫn chỉ cảm thấy một chút yuk ...)

Còn lại ở đây cho một sự lựa chọn khác ...


CẬP NHẬT: Kể từ 1,5 chỉ thị đệ quy hiện được hỗ trợ phần nào trong Angular. Điều này thu hẹp các trường hợp sử dụng cho dotjem / angular-tree rất nhiều.
Jens

3

Bạn có thể thử với mẫu Angular-Tree-DnD với Angular-Ui-Tree, nhưng tôi đã chỉnh sửa, tương thích với bảng, lưới, danh sách.

  • Có thể kéo và thả
  • Chỉ thị hàm mở rộng cho danh sách (tiếp theo, trước, getChildren, ...)
  • Lọc dữ liệu.
  • OrderBy (ver)

Cảm ơn bạn. Tôi cần Kéo và Thả, và đây dường như là giải pháp duy nhất với điều đó!
Doug

2

Dựa trên @ganaraj 's câu trả lời , và @ dnc253' s câu trả lời , tôi chỉ cần thực hiện một cách đơn giản 'chỉ thị' cho các cấu trúc cây có lựa chọn, thêm, xóa, và tính năng chỉnh sửa.

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: '=',
        }
    }
});

0

Có nó chắc chắn có thể. Câu hỏi ở đây có thể giả sử Angular 1.x, nhưng để tham khảo trong tương lai tôi bao gồm một ví dụ về Angular 2:

Về mặt khái niệm, tất cả những gì bạn phải làm là tạo một mẫu đệ quy:

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

Sau đó, bạn liên kết một đối tượng cây vào khuôn mẫu và để Angular thực hiện phép thuật của nó. Khái niệm này rõ ràng cũng có thể áp dụng cho Angular 1.x.

Dưới đây là một ví dụ đầy đủ: http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0


0

Bạn có thể sử dụng công cụ tiêm góc-góc cho đó: https://github.com/knyga/angular-recursion-injection

Cho phép bạn làm tổ sâu không giới hạn với điều hòa. Chỉ biên dịch lại nếu cần và chỉ biên dịch đúng các phần tử. Không có phép thuật trong mã.

<div class="node">
  <span>{{name}}</span>

  <node--recursion recursion-if="subNode" ng-model="subNode"></node--recursion>
</div>

Một trong những điều cho phép nó hoạt động nhanh hơn và đơn giản hơn các giải pháp khác là hậu tố "--recursion".


0

Khi cấu trúc cây lớn, Angular (tối đa 1.4.x) trở nên rất chậm trong việc hiển thị một mẫu đệ quy. Sau khi thử một số đề xuất này, cuối cùng tôi đã tạo ra một chuỗi HTML đơn giản và sử dụng ng-bind-htmlđể hiển thị nó. Tất nhiên, đây không phải là cách sử dụng các tính năng của Angular

Một hàm đệ quy đơn giản được hiển thị ở đây (với HTML tối thiểu):

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, ''));

Trong mẫu, nó chỉ cần một dòng này:

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

Điều này bỏ qua tất cả các ràng buộc dữ liệu của Angular và chỉ hiển thị HTML trong một phần nhỏ thời gian của các phương thức mẫu đệ quy.

Với cấu trúc menu như thế này (cây tệp một phần của hệ thống tệp 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'}
        ]
       }

Đầu ra trở thành:

- 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

-3

Không phức tạp.

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

mã điều khiển:

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


}]);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.