3

I wonder what is the best practice when I have a response that has many different types of objects and looks like that:

[
{"nodeClass":"Entity", "text":"foo","entityfield":"booz"},
{"nodeClass":"User","username":"bar","userfield":"baz"}
]

and I have different templates for all of those:

for Entities:

<div class="{{nodeClass}} capsule">{{entity.text}}:{{entity.entityfield}}</div>

for Users:

<div class="{{nodeClass}} capsule">{{user.username}}:{{user.userfield}}</div>

how would you structure the code and with which angularjs elements (ng-repeat etc) to (re)use the correct templates based on the value of "nodeClass". Keep in mind that I don't want to make a new conditional template except if it is the only solution.

Edit: I have found those approaches: http://onehungrymind.com/angularjs-dynamic-templates/ and if else statement in AngularJS templates and Dynamically displaying template in ng-repeat directive in AngularJS? but they are quite different than my requirements. Especially the last one is the closest to what I want but my templates as usually have different variable names in them..

Thanks

Community
  • 1
  • 1
Michail Michailidis
  • 11,792
  • 6
  • 63
  • 106
  • What is your expected structure for the overall view? or will it be multiple views? Not clear what your expectations are and that would help determine best approach – charlietfl May 07 '15 at 03:26
  • In the order they come in separate (different) divs based on their respective templates. The selection of template should be based on the property "nodeClass" – Michail Michailidis May 07 '15 at 03:28
  • how many templates are we dealing with? – tpie May 07 '15 at 03:33
  • apologies if my answer seemed premature..these comments weren't showing for some reason. – tpie May 07 '15 at 03:33
  • Some update as I was wrong up there. My templates also expect different variable names :/ - (btw this a very simplified version of my problem) - Thanks :) – Michail Michailidis May 07 '15 at 03:36
  • Based on edits in question you need to be more specific then why those other solutions don't fit your needs. And should have pointed out what you already knew so we didn't waste time doing the same things. You can use a directive for this but not sure what the complications or expectations are – charlietfl May 07 '15 at 03:56
  • ok my bad. but if that was the case then my question would be duplicate of the last one - editted – Michail Michailidis May 07 '15 at 03:58
  • 2
    @MichailMichailidis What do you mean by `different variable names`? The templates use `thing` or `entity` instead of `item` sometimes? Why can you not use the same variable name as what`ng-repeat` uses in all templates? – seangwright May 07 '15 at 04:04
  • You are not making your point as to what makes your situation different. We can't guess at what you are trying to match with your templates. Somehow you need to make a relationship between the nodeClass and template and there are lots of ways to do the matching in the code – charlietfl May 07 '15 at 04:07
  • @sgwatstack that's exactly what I mean. Well I cannot just change it in a hundred places.. – Michail Michailidis May 07 '15 at 04:09
  • Change What? you haven't shown us a sample of template names structure. – charlietfl May 07 '15 at 04:11
  • @MichailMichailidis So you already have 100s of templates which all have different variable names in them and you want to be able to stick them in `ng-repeat`s no matter what variable name the `ng-repeat` is using? – seangwright May 07 '15 at 04:12
  • @charlietfl I agree about the mapping between nodeClass property and paths - that is very easy. There are many ways as you said. But the question comes down to: is there something like this syntax so I can use my existing templates that have different variables ```ng-repeat="elem in data" ng-conditional-rename=" "``` – Michail Michailidis May 07 '15 at 04:13
  • @sgwatstack yes! Bad practice or not I need to reuse my existing templates (that are just 6) but are used in many many places – Michail Michailidis May 07 '15 at 04:14
  • sure...can use a directive...but still have to manually enter all those renames in html or in a hashmap – charlietfl May 07 '15 at 04:15
  • yeah that is not a big problem @charlietfl – Michail Michailidis May 07 '15 at 04:15
  • so any of these other solutions would work exactly the same as directive would...you need to map the templates to the nodeClass values. There is no simple way out – charlietfl May 07 '15 at 04:20
  • I agree - I know all those solutions. As I said the problem is that I am not trying to make a conditional template but reuse existing ones and the mapping is not the issue but the variable name is – Michail Michailidis May 07 '15 at 04:21
  • 1
    @MichailMichailidis what about parsing the template and doing a string replace - then return the url from `getContentUrl(objType)` to include it in the repeater. Or you could perform the string replace and insert the template into the DOM and then run `$compile(DOMElement)` on it. – seangwright May 07 '15 at 04:22
  • (Upvote) that seems like a good idea although a bit hacky. Would you mind giving me a small example? – Michail Michailidis May 07 '15 at 04:25
  • 1
    should note that this whole question got littered with a fairly significant number of comments...usually a sign that things weren't clear from the start – charlietfl May 07 '15 at 05:04
  • 1
    @MichailMichailidis I added a functioning solution. It's not pretty and there is plenty of work for you left to do, but it's a proof of concept. – seangwright May 07 '15 at 05:22

4 Answers4

4

One way is to use a dynamically generated ng-include url

HTML

  <div ng-repeat="item in data">
    <div ng-include="getContentUrl(item.nodeClass)"></div>
  </div>

Templates

  <script type="text/ng-template" id="partials/Entity.html">
    <h3>Entity Template , text= {{item.text}}</h3>
  </script>
  <script type="text/ng-template" id="partials/User.html">
    <h3>User Template , username ={{item.username}}</h3>
  </script>

JS

app.controller('MainCtrl', function($scope) {
  $scope.data=[{"nodeClass":"Entity", "text":"foo"},{"nodeClass":"User","username":"bar"}];
  $scope.getContentUrl = function(nodeClass){
    return 'partials/'+nodeClass +'.html';
  }
});

DEMO

charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • (Upvoted) Ok that is similar to http://stackoverflow.com/questions/16155542/dynamically-displaying-template-in-ng-repeat-directive-in-angularjs but my templates have different variable names.. is there a solution for that as well? – Michail Michailidis May 07 '15 at 03:55
  • not until you make your issues more definitive, Can easily make a hash map for the template paths vs nodeClass – charlietfl May 07 '15 at 03:59
  • @MichailMichailidis could you include the template name along with the data object definition in the json as a standard property name among all the varying objects? ie `{ template: "xyz.html", nodeClass: "className" ... }` – seangwright May 07 '15 at 04:01
  • The issue is that contrary to your solution and http://stackoverflow.com/questions/16155542/dynamically-displaying-template-in-ng-repeat-directive-in-angularjs my templates don't have a common variable name e.g ```item``` - but distinct ones - I updated the original question multiple times and I guess you have gotten the updates before submission. Thanks – Michail Michailidis May 07 '15 at 04:02
  • @sgwatstack I don't see why this would help my problem – Michail Michailidis May 07 '15 at 04:03
  • @charlietfl yeah I could even use a switch case statement in the getContentUrl(). But this still doesn't solve the issue of two comments above – Michail Michailidis May 07 '15 at 04:04
2

You can try pulling the template.html in the background via $http, parse the template and replace instances of the variable name that doesn't match your ng-repeat variable name. Then send that html from the template.html to a directive (or controller template) that has the ng-repeat in its template, insert the newly edited html via $('#elem').html(newHtml); and call $compile on the modified element.

The template you pull down could look like this

controller1/template1.html

<h1>{{item.data}} - {{item.name}}</h1>

Here is the template that the repeater sits in

controller1.html

<p>This is the view for controller 1.</p>

<div id="repeater" ng-repeat="thing in vm.items">

</div>

The fetching of the template, replacing of the desired string and re-compiling of the template can be done like this

controller1.js

function Controller1($scope, $http) {
    var vm = this;

    vm.items = [{name: 'item1', data: 123}, {name: 'item2', data: 456}];

    var templateReplacement = '{{thing.';
    $http.get('controller1/template1.html')
        .then(function success(response) {
            var newHtml = response.data.replace(/{{item./g, templateReplacement);
            var repeaterElem = $('#repeater');

            $(repeaterElem[0]).html(newHtml);

            $compile(repeaterElem)($scope);
    }, function failure(reason) {
            console.log(reason);
    });
}

Here is a plunk of this working in action

seangwright
  • 17,245
  • 6
  • 42
  • 54
1

I built a directive which takes two attributes - one for the template and the other for whatever data you need to pass in to the template.

Pass your template selector value through a switch statement which then will apply the correct template and sort your data appropriately. Plunker

Directive:

app.directive('templateSelector', function($compile) {
  return {
    restrict: 'A',
    scope: {},
    link: function(scope, el, attr) {
      console.log(attr.tClass, attr.tVals)
      var template ='';
      scope.data = angular.fromJson(attr.tVals);
      switch (attr.tClass) {
        case 'Entity':
          template = '<div><h1>Entity Class Template</h1>{{data.text}}</div><hr/>';
          break;
        case 'User':
          template = '<div><h1>User Class Template</h1>{{data.username}}</div><hr/>';
          break;
      }
      $template = angular.element(template);
      $compile($template)(scope);
      el.append($template); 
    }  
  }
})

HTML:

<div ng-repeat="d in dataset">
    <div template-selector t-class="{{d.nodeClass}}" t-vals="{{d}}"></div>      
</div>
tpie
  • 6,021
  • 3
  • 22
  • 41
  • No worries for the premature answer. But this renders all the values and doesn't use predefined (different) templates – Michail Michailidis May 07 '15 at 03:35
  • one for each of the different nodeClasses - In my real case around 6. In this case 2 – Michail Michailidis May 07 '15 at 03:37
  • is this the structure of the dataset? Just a template selector and a value you need to display? – tpie May 07 '15 at 03:38
  • it is a set of values and a property that differentiates the template to be used – Michail Michailidis May 07 '15 at 03:38
  • see if that solution works for you. I had included templates inside the directive itself, but if you have more complex datasets and templates you would definitely want to abstract those out of the directive. You can just have the switch function set the templateUrl value as show in this post: http://stackoverflow.com/questions/21835471/angular-js-directive-dynamic-templateurl – tpie May 07 '15 at 04:14
  • hate to say this but using switch is uglier than alternatives like using a hashmap when there will be many of these – charlietfl May 07 '15 at 04:17
  • @tpie thanks for your effort. This solution doesn't use included templates from a file but that is minor. The major problem is that in this solution as well as on the other proposed ones the variable is the same and in my templates the variable names are distinct. – Michail Michailidis May 07 '15 at 04:18
  • @charlietfl switch, hash or if else or path naming convention - that is minor ;) – Michail Michailidis May 07 '15 at 04:18
  • @charlietfl - he said about 6...not that many. I am slightly confused though. You say the variable is the same, but in your templates the variable names are distinct. I am not following what you mean here. – tpie May 07 '15 at 04:21
  • @tpie I mean I have a template with entity, a template with user variable etc.. And I have pages that use them in this fashion ng-repeat over users using the variable user, entity etc.. the templates are 6 in my real case the pages are many and I don't want to make them all use element/item.. – Michail Michailidis May 07 '15 at 04:23
  • are you saying that in the templates you want it to dynamically display the data, without having to put in specific bindings like {{data.username}}? – tpie May 07 '15 at 04:27
1

I might modify the data from the service before it ever reaches the view. This works by using ng-if to determine which HTML to display:

<div ng-repeat="data in dataset">
  <div
    class="capsule"
    ng-class="data.nodeClass"
    ng-bind="data.text"
    ng-if="data.nodeClass==='Entity'"></div>
  <div
    class="capsule"
    ng-class="data.nodeClass"
    ng-bind="data.username"
    ng-if="data.nodeClass==='User'"></div>
</div>

Plunker Forked from tpie

Marcus Showalter
  • 911
  • 1
  • 8
  • 10