11

I am attempting to dynamically display one of several templates within an ng-repeat directive, based on the current item.

My JSON data looks like this:

data: {
    groups: [
        {
            name: "Group 1",                
            sections: [
                { name: "Section A" },
                { name: "Section B" }
            ]
        },
        {
            name: "Group 2",                
            sections: [
                { name: "Section A" },
                { name: "Section B" }
            ]
        }
    ]
}

My goal is to render the tree of data dynamically, with each group containing multiple sections. The groups will all have the same template, but each section should have its own template, based on the name field.

Assuming the top level HTML is:

<div ng-repeat="group in groups">
    {{ group.name }}
    <div ng-repeat="section in sections">
        <!-- Dynamic section template used -->
    </div>
</div>

Ideally, each section would also need to have its own scoped data and controller associated with it. I've had good luck building this type of system with Knockout but am trying to understand the Angular way of doing things.

Brian Vallelunga
  • 9,869
  • 15
  • 60
  • 87
  • Sounds like a good case for a directive. – lucuma Apr 22 '13 at 19:56
  • Lucuma, could you comment more? I've thought of using directives too, but am unsure of the ideal level of granularity. Should I build a directive that just does the selection of templates, or should each "section" get its own directive? – Brian Vallelunga Apr 23 '13 at 12:17
  • I think it depends on how you want to make it. You could create one directive to do all of it (pass in the groups array) and/or you can create another directive that is called inside your groups directive to handle the sections. The benefit of a directive is they get their own scope. I'll provide a small example. – lucuma Apr 23 '13 at 13:19

2 Answers2

34

You could do something like this:

<div ng-repeat="group in groups">
    {{ group.name }}
    <div ng-repeat="section in sections" ng-include="getIncludeFile(section)">
        <!-- Dynamic section template used -->
    </div>
</div>

Then in your controller:

$scope.getIncludeFile = function(section) {
    // Make this more dynamic, but you get the idea
    switch (section) {
        case "Section A":
            return 'partials/sectiona.html';
        case "Section B":
            return 'partials/sectionb.html';
    }
}

Then your sectiona.html could look like this (to have a controller specific to that file):

<div ng-controller="SectionAController">
    <!-- HTML in here, and can bind straight to your SectionAController -->
</div>
Jason Aden
  • 2,823
  • 1
  • 18
  • 13
  • This is the most straightforward method for my uses right now. If my needs get more complicated, I may go with the Directive approach. – Brian Vallelunga Apr 29 '13 at 19:53
  • what if i wanted to turn the json items in to a list of links which would route to a new page... All of these pages would follow the same template with unique properties like a post-type.... But i dont need a db or an admin. Just need a new page for each data item with the same view template. – Omar Jan 12 '17 at 18:07
4

In the past month there was a checkin to angular for supporting dynamic templates in a directive however I wasn't able to find very much information with regards to its use. Here is the reference. https://github.com/angular/angular.js/pull/1849

Although this still uses the the same nginclude it is all encapsulated in two directives:

Demo: http://plnkr.co/edit/4DIlHMNlHQ8Wm9XHNycH?p=preview

HTML:

<groups-control groupdata="groups"></groups-control>

Controller:

app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';
  var json = {data: {
    groups: [
        {
            name: "Group 1",                
            sections: [
                { name: "Section A" },
                { name: "Section B" }
            ]
        },
        {
            name: "Group 2",                
            sections: [
                { name: "Section A" },
                { name: "Section B" }
            ]
        }
    ]
  }};
  $scope.groups = json.data.groups;

}); 

Directive split into two:

app.directive('groupsControl', function(){
    return {
      restrict: 'E',

      replace: true,
      transclude: false,
      scope: { items:'=groupdata'},

      template: '<div ng-repeat="group in items">' +
                  '{{ group.name }}' +
                  '<section-control sections="group.sections" />'+

              '</div>',
      // The linking function will add behavior to the template
      link: function(scope, element, attrs) {


      }
    }
  }).directive('sectionControl', function(){
    return {
      restrict: 'E',

      replace: true,
      transclude: false,
      scope: { items:'=sections'},

      template: '<div ng-repeat="section in items" ng-include="getIncludeFile(section)">'+
                '</div>',

      link: function(scope, element, attrs) {
        scope.getIncludeFile = function(section) {
            return section.name.toLowerCase().replace('section ','') + ".html";
        }

      }
    }
  });

I'd actually like to see someone post an answer using a function for the templateUrl based on some of the scope data.

lucuma
  • 18,247
  • 4
  • 66
  • 91