Rekurencja w dyrektywach kątowych

Istnieje kilka popularnych pytań i odpowiedzi dotyczących rekurencyjnej dyrektywy kątowej, które sprowadzają się do jednego z następujących rozwiązań:

Pierwszy ma problem, że nie można usunąć wcześniej skompilowanego kodu, chyba że w sposób zrozumiały zarządzasz ręcznym procesem kompilacji. drugie podejście ma problem... nie jest to dyrektywa i brakuje jej potężnych możliwości, ale co pilniejsze, nie można jej parametryzować tak, jak dyrektywa może być; jest ona po prostu powiązana z nową instancją kontrolera.

I ' ve been playing with ręczne wykonywanie angular.bootstrap LUB @compile() w funkcji link, ale pozostawia mi to problem ręcznego śledzenia elementów do usunięcia i dodania.

Czy istnieje dobry sposób na posiadanie parametryzowanego wzorca rekurencyjnego, który zarządza dodawaniem / usuwaniem elementów w celu odzwierciedlenia stanu środowiska uruchomieniowego? To znaczy drzewo z przyciskiem Dodaj / Usuń węzeł i jakimś polem wejściowym, którego wartość jest przekazywana do węzłów potomnych węzła. Być może połączenie drugiego podejścia z przykutymi lunetami (ale nie mam pojęcia jak aby to zrobić)?

Author: Community, 2013-01-21

9 answers

Zainspirowany rozwiązaniami opisanymi w wątku wspomnianym przez @ dnc253, wyodrębniłem funkcjonalność rekurencji do usługi.

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

Który jest używany w następujący sposób:

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

Zobacz to Plunker na demo. Najbardziej podoba mi się to rozwiązanie ponieważ:

  1. nie potrzebujesz specjalnej dyrektywy, która czyni Twój html mniej czystym.
  2. logika rekurencji jest abstrakcyjna do usługi RecursionHelper, więc zachowujesz swoje dyrektywy czysto.
 307
Author: Mark Lagendijk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-20 12:50:37

Ręczne dodawanie i kompilowanie elementów jest zdecydowanie doskonałym podejściem. Jeśli używasz ng-repeat, nie będziesz musiał ręcznie usuwać elementów.

Demo: 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);
    }
}
});
 23
Author: SunnyShah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-13 11:59:20

Nie wiem na pewno, czy to rozwiązanie znajduje się w jednym z podlinkowanych przykładów, czy w tym samym podstawowym pojęciu, ale potrzebowałem dyrektywy rekurencyjnej i znalazłem świetne, łatwe rozwiązanie .

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

Należy utworzyć dyrektywę recursive, a następnie owinąć ją wokół elementu, który wywołuje wywołanie rekurencyjne.

 12
Author: dnc253,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-02 00:30:54

Od kąta 1.5.x, nie są wymagane żadne sztuczki, następujące czynności są możliwe. Koniec z brudną robotą!

To odkrycie było produktem mojego poszukiwania lepszego / czystszego rozwiązania dla dyrektywy rekurencyjnej. Znajdziesz go tutaj https://jsfiddle.net/cattails27/5j5au76c / . obsługuje jak dotąd 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="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>
 8
Author: jkris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-29 05:59:51

Po użyciu kilku obejść przez jakiś czas, wielokrotnie wracałem do tego problemu.

Nie jestem zadowolony z rozwiązania usługi, ponieważ działa ono dla dyrektyw, które mogą wprowadzić usługę, ale nie działa dla anonimowych fragmentów szablonów.

Podobnie rozwiązania, które zależą od specyficznej struktury szablonów poprzez manipulację DOM w dyrektywie, są zbyt specyficzne i kruche.

Mam to, co uważam za ogólne rozwiązanie, które enkapsuluje rekurencję jako dyrektywa sama w sobie, która w minimalnym stopniu koliduje z innymi dyrektywami i może być używana anonimowo.

Poniżej demonstracja, którą można również pobawić się w 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="[email protected]" 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>
 3
Author: tilgovi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-19 21:35:12

Teraz, gdy Angular 2.0 jest już w podglądzie, myślę, że można dodać alternatywę Angular 2.0 do miksu. Przynajmniej będzie to korzystne dla ludzi później:

Kluczową koncepcją jest zbudowanie rekurencyjnego szablonu z odniesieniem do siebie:

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

Następnie przywiązujesz obiekt drzewa do szablonu i obserwujesz, jak rekurencja zajmuje się resztą. Oto pełny przykład: http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0

 2
Author: TGH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-30 22:01:13

Istnieje naprawdę bardzo proste obejście tego problemu, które w ogóle nie wymaga dyrektyw.

Cóż, w tym sensie, może nie jest to nawet rozwiązanie pierwotnego problemu, jeśli założysz, że potrzebujesz dyrektyw, ale jest to rozwiązanie, jeśli chcesz rekurencyjną strukturę GUI z parametryzowanymi pod-strukturami GUI. Pewnie tego chcesz.

Rozwiązanie opiera się na wykorzystaniu ng-controller, ng-init i ng-include. Wystarczy zrobić to w następujący sposób, Załóżmy, że kontroler nazywa się" MyController", Twój szablon znajduje się w myTemplate.html i że masz funkcję inicjalizacji na kontrolerze o nazwie init, która pobiera argumenty A, B I C, umożliwiając parametryzację kontrolera. Wtedy rozwiązanie jest następujące:

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>

Odkryłem przez plain conincidence, że tego rodzaju struktura może być rekurencyjna, jak chcesz w plain vanilla angular. Po prostu postępuj zgodnie z tym wzorcem projektowym i możesz używać rekurencyjnych struktur interfejsu użytkownika bez zaawansowanej kompilacji itp.

Wewnątrz kontrolera:

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

Jedyny minus, jaki widzę, to niezgrabna składnia, z którą musisz się pogodzić.

 2
Author: erobwen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-17 11:54:37

Możesz użyć do tego angular-recursion-injector: https://github.com/knyga/angular-recursion-injector

Pozwala na nieograniczone zagnieżdżanie głębokości z kondycjonowaniem. Rekompiluje tylko w razie potrzeby i kompiluje tylko właściwe elementy. Żadnej magii w kodzie.

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

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

Jedną z rzeczy, która pozwala mu działać szybciej i prościej niż inne rozwiązania jest przyrostek "-- recursion".

 0
Author: Oleksandr Knyga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-12 13:35:22

Skończyłem tworząc zestaw podstawowych dyrektyw dla rekurencji.

IMO jest to o wiele bardziej podstawowe rozwiązanie niż tutaj, i tak samo elastyczne, jeśli nie bardziej, więc nie jesteśmy zobowiązani do używania struktur UL / LI itp... Ale oczywiście mają one sens, jednak dyrektywy nie są tego świadome...

Bardzo prostym przykładem może być:

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

Implementacja 'DX-start-with' I 'DX-connect' znajduje się na: https://github.com/dotJEM/angular-tree

Oznacza to, że nie musisz tworzyć 8 dyrektyw, jeśli potrzebujesz 8 różnych układów.

Utworzenie widoku drzewa na szczycie, w którym można dodawać lub usuwać węzły, byłoby raczej proste. Jak w: 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>

Od tego momentu, kontroler i szablon mogą być zawarte w jego własnej dyrektywie, jeśli ktoś sobie tego życzy.

 0
Author: Jens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-24 11:02:16