179

一般的な再帰的な angular ディレクティブに関する Q&A がいくつかありますが、これらはすべて次の解決策のいずれかに帰着します。

最初のものには、手動のコンパイル プロセスを包括的に管理しない限り、以前にコンパイルされたコードを削除できないという問題があります。2 番目のアプローチには、ディレクティブではなく、その強力な機能を利用できないという問題があります。新しいコントローラー インスタンスにバインドされているだけです。

angular.bootstrapリンク関数でorを手動で実行して遊んでい@compile()ますが、削除および追加する要素を手動で追跡するという問題が残ります。

実行時の状態を反映するために要素の追加/削除を管理するパラメーター化された再帰パターンを持つ良い方法はありますか? つまり、ノードの追加/削除ボタンと、値がノードの子ノードに渡される入力フィールドを持つツリーです。おそらく、チェーンされたスコープを使用した2番目のアプローチの組み合わせです(ただし、これを行う方法がわかりません)?

4

9 に答える 9

25

要素を手動で追加してコンパイルすることは、間違いなく完璧なアプローチです。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);
    }
}
});
于 2013-01-22T14:01:21.073 に答える
12

この解決策が、リンクした例の 1 つにあるのか、同じ基本概念にあるのかはわかりませんが、再帰ディレクティブが必要で、優れた簡単な解決策を見つけました。

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

ディレクティブを作成してrecursiveから、再帰呼び出しを行う要素をラップする必要があります。

于 2013-02-02T00:30:54.523 に答える
11

Angular 1.5.x の時点で、これ以上のトリックは必要ありません。以下が可能になりました。汚い回避策はもう必要ありません。

この発見は、再帰ディレクティブのより優れた/よりクリーンなソリューションを探した結果でした。ここで見つけることができますhttps://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="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>

于 2016-07-28T09:34:41.243 に答える
4

しばらくの間、いくつかの回避策を使用した後、この問題に繰り返し戻ってきました。

サービスを注入できるディレクティブでは機能しますが、匿名のテンプレート フラグメントでは機能しないため、サービス ソリューションには満足できません。

同様に、ディレクティブで DOM 操作を行うことによって特定のテンプレート構造に依存するソリューションは、あまりにも具体的で脆弱です。

私は、再帰を他のディレクティブとの干渉を最小限に抑え、匿名で使用できる独自のディレクティブとしてカプセル化する一般的なソリューションであると信じています。

以下は、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>

于 2015-04-19T21:35:12.553 に答える
2

Angular 2.0 がプレビュー段階にあるので、Angular 2.0 の代替案をミックスに追加しても問題ないと思います。少なくとも、後で人々に利益をもたらします。

重要な概念は、自己参照を使用して再帰的なテンプレートを作成することです。

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

次に、ツリー オブジェクトをテンプレートにバインドし、再帰が残りを処理するのを確認します。完全な例を次に示します: http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0

于 2015-05-30T22:01:13.513 に答える
2

これには、ディレクティブをまったく必要としない非常に簡単な回避策があります。

その意味で、ディレクティブが必要だと仮定する場合、元の問題の解決策でさえないかもしれませんが、GUI のパラメーター化されたサブ構造を持つ再帰的な GUI 構造が必要な場合は解決策です。これはおそらくあなたが望むものです。

このソリューションは、ng-controller、ng-init、および ng-include の使用のみに基づいています。次のように実行してください。コントローラーが「MyController」と呼ばれ、テンプレートが myTemplate.html にあり、コントローラーに引数 A、B、および C を受け取る init という初期化関数があると仮定して、コントローラーをパラメーター化します。次に、解決策は次のとおりです。

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>

この種の構造は、単純なバニラ角度で好きなように再帰的にできることを偶然に発見しました。この設計パターンに従うだけで、高度なコンパイルなどをいじる必要なく、再帰的な 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
} 

私が見ることができる唯一の欠点は、あなたが我慢しなければならないぎこちない構文です。

于 2015-08-24T20:44:03.287 に答える
0

最終的に、再帰用の基本的なディレクティブのセットを作成しました。

IMO ここで見つかったソリューションよりもはるかに基本的であり、それ以上ではないにしても柔軟であるため、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 つの異なるレイアウトが必要な場合でも、8 つのディレクティブを作成する必要はありません。

その上にノードを追加または削除できるツリービューを作成するのはかなり簡単です。のように: 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>

この時点から、必要に応じてコントローラーとテンプレートを独自のディレクティブでラップできます。

于 2014-06-09T18:16:15.123 に答える
0

そのために 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>

他のソリューションよりも高速かつ簡単に機能することを可能にするものの1つは、「--recursion」サフィックスです。

于 2015-06-12T13:35:22.480 に答える