How can I make recursive templates in AngularJS wh

2019-01-01 10:24发布

问题:

I\'m trying to build a form dynamically from a JSON object, which contains nested groups of form elements:

  $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\"]}
    ]},
  ];

I\'ve been using ng-switch blocks, but it becomes untenable with nested items, like in the Address object above.

Here\'s the fiddle: http://jsfiddle.net/hairgamiMaster/dZ4Rg/

Any ideas on how to best approach this nested problem? Many thanks!

回答1:

I think that this could help you. It is from an answer I found on a Google Group about recursive elements in a tree.

The suggestion is from Brendan Owen: 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>

The proposed solution is about using a template that uses the ng-include directive to call itself if the current element has children.

In your case, I would try to create a template with the ng-switch directive (one case per type of label like you did) and add the ng-include at the end if there are any child labels.



回答2:

Combining what @jpmorin and @Ketan suggested (slight change on @jpmorin\'s answer since it doesn\'t actually work as is)...there\'s an ng-if to prevent \"leaf children\" from generating unnecessary ng-repeat directives:

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

here\'s the working version in Plunker



回答3:

Might consider using ng-switch to check availability of Fields property. If so, then use a different template for that condition. This template would have an ng-repeat on the Fields array.



回答4:

I know this is an old question, but for others who might come by here though a search, I though I would leave a solution that to me is somewhat more neat.

It builds on the same idea, but rather than having to store a template inside the template cache etc. I wished for a more \"clean\" solution, so I ended up creating https://github.com/dotJEM/angular-tree

It\'s fairly simple to use:

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

Since the directive uses transclusion instead of compile (as of the latest version), this should perform better than the ng-include example.

Example based on the Data here:

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:

Just want to extend jpmorin post in case of property based structure:

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

As you can see json object here doesn\'t contain array structure.

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>

However you can iterate over it.

Angular documentation for ng-repeat over properties here

And some row implementation here