Ist es möglich, eine Baumansicht mit Angular zu erstellen?

177

Ich möchte Daten in einer Baumstruktur in einer Web-App anzeigen. Ich hatte gehofft, Angular für diese Aufgabe verwenden zu können.

Es sieht so aus, als würde ng-repeat es mir ermöglichen, eine Liste von Knoten zu durchlaufen, aber wie kann ich dann verschachteln, wenn die Tiefe eines bestimmten Knotens zunimmt?

Ich habe den folgenden Code ausprobiert , aber das automatische Escapezeichen des HTML- Codes verhindert , dass dies funktioniert. Außerdem befindet sich das end ul-Tag an der falschen Stelle.

Ich bin mir ziemlich sicher, dass ich dieses Problem völlig falsch angehe.

Irgendwelche Ideen?

Jon Abrams
quelle
Ich habe dies nur auf eine ziemlich allgemeine Art und Weise auf eine andere Frage beantwortet: stackoverflow.com/questions/14430655/…
tilgovi

Antworten:

231

Schauen Sie sich diese Geige an

Original: http://jsfiddle.net/brendanowen/uXbn6/8/

Aktualisiert: http://jsfiddle.net/animaxf/uXbn6/4779/

Dies sollte Ihnen eine gute Vorstellung davon geben, wie ein tree like structureVerwendungswinkel angezeigt wird . Es ist eine Art Rekursion in HTML!

Ganaraj
quelle
94
Warum nicht Ihre Quelle angeben ? Du hast einen Beitrag in diesem Thread geschrieben und jetzt postest du hier eine URL mit deinem eigenen Namen?
Janus Troelsen
5
Hier ist eine identische Version (glaube ich), außer dass sie (zumindest für mich) viel schneller geladen wird, da im CSS-Bereich kein Twitter-Bootstrap eingefügt ist. jsfiddle.net/brendanowen/uXbn6/8
KajMagnus
10
Alter, du solltest deine Quelle angeben.
Ajax3.14
46
Ich war es wirklich leid, dass Leute ständig kommentierten, dass die URL meinen Namen enthält (und deshalb ist es Plagiat!). So funktioniert jsfiddle leider. Wenn Sie etwas teilen, während Sie angemeldet sind, behält es Ihren Benutzernamen. Trotzdem habe ich jetzt auf die ursprüngliche URL verlinkt. Eine Antwort ablehnen, wenn sie falsch ist - Die Antwort ist in diesem Szenario richtig, und die Sicherungs-URL, die ich hatte, scheint meinen Namen zu enthalten.
Ganaraj
5
Ich habe gerade die Schaltfläche zum Reduzieren
jbaylina
77

Wenn Sie Bootstrap CSS verwenden ...

Ich habe eine einfache wiederverwendbare Baumsteuerung (Direktive) für AngularJS basierend auf einer Bootstrap "nav" -Liste erstellt. Ich habe zusätzliche Einrückungen, Symbole und Animationen hinzugefügt. Für die Konfiguration werden HTML-Attribute verwendet.

Es wird keine Rekursion verwendet.

Ich nannte es Angular-Bootstrap-Nav-Tree (eingängiger Name, findest du nicht?)

Es ist ein Beispiel hier , und die Quelle ist hier .

Nick Perkins
quelle
1
Es ist wunderschön, aber seien Sie gewarnt, es funktioniert nicht in Angular 1.0.x Branch.
Danita
3
Ja, es verwendet das neue Animationsmaterial ... erfordert Angular 1.1.5 (glaube ich?)
Nick Perkins
3
UPDATE: Es funktioniert jetzt entweder mit Angular 1.1.5 oder Angular 1.2.0 und auch mit Bootsrap 2 oder Bootstrap 3
Nick Perkins
1
Nur zu Ihrer Information, wenn Sie Bower verwenden, hat Nick dies jetzt für eine einfache Installation verfügbar gemacht - "Bower Search Angular-Bootstrap-Nav-Tree" und "Bower Install Angular-Bootstrap-Nav-Tree - Speichern" - und Sie sind fertig.
Arcseldon
2
@ Nick Perkins - können Sie bitte erklären, warum Ihr Angular-Bootstrap-Nav-Baum keine API zum Entfernen eines Zweigs / Knotens hat. Zumindest nach einer schnellen Überprüfung der Quelle und der Überprüfung Ihrer Tests / Beispiele scheint diese Option nicht verfügbar zu sein. Dies ist sicherlich eine kritische Auslassung?
Arcseldon
35

Wenn Sie so etwas machen, ist die beste Lösung eine rekursive Direktive. Wenn Sie jedoch eine solche Anweisung erstellen, stellen Sie fest, dass AngularJS in eine Endlosschleife gerät.

Die Lösung hierfür besteht darin, dass die Direktive das Element während des Kompilierungsereignisses entfernt und es manuell kompiliert und zu den Verknüpfungsereignissen hinzufügt.

Ich habe dies in diesem Thread herausgefunden und diese Funktionalität in einen Dienst abstrahiert .

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

Mit diesem Dienst können Sie einfach eine Baumanweisung (oder andere rekursive Anweisungen) erstellen. Hier ist ein Beispiel für eine Baumanweisung:

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

In diesem Plunker finden Sie eine Demo. Diese Lösung gefällt mir am besten, weil:

  1. Sie benötigen keine spezielle Direktive, die Ihr HTML weniger sauber macht.
  2. Die Rekursionslogik wird in den RecursionHelper-Dienst abstrahiert, sodass Sie Ihre Anweisungen sauber halten.

Update: Unterstützung für benutzerdefinierte Verknüpfungsfunktionen hinzugefügt.

Mark Lagendijk
quelle
1
das scheint so ordentlich und mächtig zu sein, eine Idee, warum dies kein Standardverhalten in Angularjs ist?
Paul
Wie fügt man bei der Verwendung von "compile" dem Bereich zusätzliche Attribute hinzu? Die "Link" -Funktion scheint nicht mehr verfügbar zu sein, sobald "Kompilieren" vorhanden ist ...
Brian Kent
1
@ bkent314 Ich habe Unterstützung dafür hinzugefügt. Es akzeptiert jetzt Verknüpfungsfunktionen auf die gleiche Weise, wie Compile sie zurückgeben kann. Ich habe auch ein Github-Projekt für den Dienst erstellt.
Mark Lagendijk
@ MarkLagendijk Sehr, sehr schick! Sie verdienen viele positive Stimmen, wenn Sie die Rekursion aus der Richtlinie abstrahieren. Alle Anweisungen, die ich gesehen habe, sehen mit dieser Logik hoffnungslos kompliziert aus. Gibt es eine Möglichkeit, Ihren RecursionHelper mit Transklusion arbeiten zu lassen?
Acjay
Ich schlage wirklich vor, dass Sie einige Daten auf diese Art von Lösung werfen - ja, fast jeder implementiert einen Baum mit rekursiven Anweisungen, es ist einfach. Aber es ist extrem langsam wie ng-repeat $ Digests - sobald Sie Hunderte von Knoten erreicht haben, funktioniert dies nicht mehr.
Artemiy
17

Angular-UI-Tree scheint einen guten Job für mich zu machen

Kalyanaraman Santhanam
quelle
15

Hier ist ein Beispiel mit einer rekursiven Direktive: http://jsfiddle.net/n8dPm/ Entnommen aus 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); 
            });
        };
    }
};
});
Savagepanda
quelle
Ich habe damit experimentiert und möchte auch die Transklusion verwenden. Glaubst du, dass das möglich ist?
L. Trabacchin
5

Ein weiteres Beispiel, das auf der Originalquelle basiert , mit einer bereits vorhandenen Beispielbaumstruktur (einfacher zu sehen, wie es funktioniert, IMO) und einem Filter zum Durchsuchen des Baums:

JSFiddle

GFoley83
quelle
4

So viele großartige Lösungen, aber ich habe das Gefühl, dass sie alle die Dinge auf die eine oder andere Weise etwas komplizieren.

Ich wollte etwas erstellen, das die Einfachheit von @Mark Lagendijks Awnser wiederherstellt, ohne jedoch eine Vorlage in der Direktive zu definieren, sondern den "Benutzer" die Vorlage in HTML erstellen lassen ...

Mit Ideen aus https://github.com/stackfull/angular-tree-repeat usw. habe ich das Projekt erstellt: https://github.com/dotJEM/angular-tree

So können Sie Ihren Baum wie folgt bauen:

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

Was für mich sauberer ist, als mehrere Direktiven für unterschiedlich strukturierte Bäume erstellen zu müssen ... Im Wesentlichen ist es ein bisschen falsch, das Obige als Baum zu bezeichnen. Es greift viel mehr von @ ganarajs Antwort auf "rekursive Vorlagen" auf, erlaubt es uns aber Definieren Sie die Vorlage, in der wir den Baum benötigen.

(Sie könnten das mit einer Skript-Tag-basierten Vorlage tun, aber sie muss immer noch direkt außerhalb des eigentlichen Baumknotens sitzen, und es fühlt sich immer noch ein bisschen yuk an ...)

Links hier für nur eine andere Wahl ...

Jens
quelle
UPDATE: Ab 1.5 werden rekursive Direktiven in Angular nun etwas nativ unterstützt. Dies schränkt die Anwendungsfälle für Dotjem / Angular-Tree erheblich ein.
Jens
3

Sie können mit Angular-Tree-DnD- Beispiel mit Angular-Ui-Tree versuchen , aber ich bearbeitet, Kompatibilität mit Tabelle, Raster, Liste.

  • Drag & Drop möglich
  • Erweiterte Funktionsanweisung für Liste (next, prev, getChildren, ...)
  • Daten filtern.
  • OrderBy (ver)
Nguyễn Thiện Hùng
quelle
Danke. Ich brauchte Drag & Drop, und dies scheint die einzige Lösung dafür zu sein!
Doug
2

Basierend auf @ganaraj ‚s Antwort , und @ dnc253‘ s Antwort , ich nur eine einfache ‚Richtlinie‘ für die Baumstruktur mit Auswahl gemacht, das Hinzufügen, Löschen und Funktion bearbeiten.

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: '=',
        }
    }
});
Tsuneo Yoshioka
quelle
0

Ja, das ist definitiv möglich. Die Frage hier geht wahrscheinlich von Angular 1.x aus, aber zum späteren Nachschlagen füge ich ein Angular 2-Beispiel hinzu:

Konzeptionell müssen Sie lediglich eine rekursive Vorlage erstellen:

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

Anschließend binden Sie ein Baumobjekt an die Vorlage und lassen Angular seine Magie wirken. Dieses Konzept ist offensichtlich auch auf Angular 1.x anwendbar.

Hier ist ein vollständiges Beispiel: http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0

TGH
quelle
0

Sie können dafür den Winkelrekursionsinjektor verwenden: https://github.com/knyga/angular-recursion-injector

Ermöglicht das Verschachteln mit unbegrenzter Tiefe und Konditionierung. Neukompilierung nur bei Bedarf und Kompilierung nur der richtigen Elemente. Keine Magie im Code.

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

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

Eines der Dinge, die es ermöglichen, schneller und einfacher als die anderen Lösungen zu arbeiten, ist das Suffix "--recursion".

Oleksandr Knyga
quelle
0

Wenn die Baumstruktur groß ist, wird Angular (bis zu 1.4.x) beim Rendern einer rekursiven Vorlage sehr langsam. Nachdem ich einige dieser Vorschläge ausprobiert hatte, erstellte ich eine einfache HTML-Zeichenfolge und verwendete sie ng-bind-htmlzum Anzeigen. Dies ist natürlich nicht die Möglichkeit, Angular-Funktionen zu verwenden

Hier wird eine rekursive Bare-Bones-Funktion gezeigt (mit minimalem HTML-Code):

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

In der Vorlage wird nur diese eine Zeile benötigt:

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

Dies umgeht die gesamte Datenbindung von Angular und zeigt den HTML-Code einfach in einem Bruchteil der Zeit der rekursiven Vorlagenmethoden an.

Mit einer Menüstruktur wie dieser (ein partieller Dateibaum eines Linux-Dateisystems):

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'}
        ]
       }

Die Ausgabe wird:

- 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
Brent Washburne
quelle
-3

Nicht kompliziert.

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

Controller-Code:

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


}]);
MBK
quelle