Как я могу сделать рекурсивные шаблоны в AngularJS при использовании вложенных объектов?

Я пытаюсь построить форму динамически с объекта JSON, который содержит вложенные группы элементов формы:

  $scope.formData = [
  {label:'First Name', type:'text', required:'true'},
  {label:'Last Name', type:'text', required:'true'},
  {label:'Coffee Preference', type:'dropdown', options: ["HiTest", "Dunkin", "Decaf"]},
  {label: 'Address', type:'group', "Fields":[
      {label:'Street1', type:'text', required:'true'},
      {label:'Street2', type:'text', required:'true'},
      {label:'State', type:'dropdown',  options: ["California", "New York", "Florida"]}
    ]},
  ];

Я использую блоки ng-switch, но он становится несостоятельным с вложенными элементами, например, в указанном выше объекте Address.

Здесь скрипка: http://jsfiddle.net/hairgamiMaster/dZ4Rg/

Любые идеи о том, как наилучшим образом подойти к этой вложенной проблеме? Большое спасибо!

Ответ 1

Я думаю, что это может вам помочь. Это ответ, который я нашел в Google Group об рекурсивных элементах дерева.

Предложение от Брендана Оуэна: http://jsfiddle.net/brendanowen/uXbn6/8/

<script type="text/ng-template" id="field_renderer.html">
    {{data.label}}
    <ul>
        <li ng-repeat="field in data.fields" ng-include="'field_renderer.html'"></li>
    </ul>
</script>

<ul ng-controller="NestedFormCtrl">
    <li ng-repeat="field in formData" ng-include="'field_renderer.html'"></li>
</ul>

Предлагаемое решение заключается в использовании шаблона, который использует директиву ng-include для вызова себя, если текущий элемент имеет дочерние элементы.

В вашем случае я бы попытался создать шаблон с директивой ng-switch (по одному случаю на тип ярлыка, как и вы), и добавить конец ng-include в конце, если есть дочерние метки.

Ответ 2

Объединив то, что предложили @jpmorin и @Ketan (небольшое изменение в ответе @jpmorin, так как оно фактически не работает как есть)... там ng-if, чтобы предотвратить "листовые дети" от создания ненужных директив ng-repeat:

<script type="text/ng-template" id="field_renderer.html">
  {{field.label}}
  <ul ng-if="field.Fields">
      <li ng-repeat="field in field.Fields" 
         ng-include="'field_renderer.html'">
      </li>
  </ul>
</script>
<ul>
  <li ng-repeat="field in formData" ng-include="'field_renderer.html'"></li>
</ul>

здесь рабочая версия в Plunker

Ответ 3

Можете рассмотреть возможность использования ng-switch для проверки доступности свойства Fields. Если это так, используйте другой шаблон для этого условия. Этот шаблон будет иметь ng-repeat в массиве Fields.

Ответ 4

Я знаю, что это старый вопрос, но для других, которые могут прийти сюда, хотя поиск, хотя я бы оставил решение, которое для меня несколько более аккуратное.

Он основывается на той же идее, но вместо того, чтобы хранить шаблон внутри кеша шаблона и т.д., я пожелал более "чистого" решения, поэтому я закончил создание 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>

Поскольку в директиве вместо компиляции используется переход (с последней версией), это должно работать лучше, чем пример ng-include.

Пример, основанный на данных здесь:

angular
  .module('demo', ['dotjem.angular.tree'])
  .controller('AppController', function($window) {

    this.formData = [
      { label: 'First Name', type: 'text', required: 'true' },
      { label: 'Last Name',  type: 'text', required: 'true' }, 
      { label: 'Coffee Preference', type: 'dropdown', options: ["HiTest", "Dunkin", "Decaf"] }, 
      { label: 'Address', type: 'group',
      "Fields": [{
        label: 'Street1', type: 'text', required: 'true' }, {
        label: 'Street2', type: 'text', required: 'true' }, {
        label: 'State',   type: 'dropdown', options: ["California", "New York", "Florida"]
      }]
    }, ];

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

   <form>
        <ul class="unstyled" dx-start-with="app.formData" >
            <li ng-repeat="field in $dxPrior" data-ng-switch on="field.type">
                <div data-ng-switch-when="text">
                    <label>{{field.label}}</label>
                    <input type="text"/>
                </div>
                <div data-ng-switch-when="dropdown">
                    <label>{{field.label}}</label>
                    <select>
                        <option ng-repeat="option in field.options" value="{{option}}">{{option}}</option>
                    </select>
                </div>
                <div data-ng-switch-when="group" class="well">
                    <h2>{{field.label}}</h2>
                    <ul class="unstyled" dx-connect="field.Fields" />
                </div>   
            </li>
        </ul>
            <input class="btn-primary" type="submit" value="Submit"/>
    </form>
  
  <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>

Ответ 5

Просто хочу расширить пост jpmorin в случае структуры, основанной на свойствах:

 JSON:
{
          "id": 203,
          "question_text_id": 1,
          "yes": {
            "question_text_id": 25,
            "yes": {
              "question_text_id": 26
            }
          },
          "no": {
            "question_text_id": 4
          }
        }

Как вы можете видеть, объект json здесь не содержит структуры массива.

HTML

<div>
    <script type="text/ng-template" id="tree_item_renderer.html">
        <span>{{key}} {{value}}</span>
        <ul>
            <li ng-repeat="(key,value) in value.yes" ng-include="'tree_item_renderer.html'"></li>
            <li ng-repeat="(key,value) in value.no" ng-include="'tree_item_renderer.html'"></li>
        </ul>
    </script>

    <ul>
        <li ng-repeat="(key,value) in symptomeItems" ng-include="'tree_item_renderer.html'"></li>
    </ul>
</div>

Однако вы можете перебирать его.

Angular документация для свойств ng-repeat поверх здесь

И некоторая реализация строки здесь