1

I would like display a list of objects from my controller using a directive. Inside that directive I'd like to be able to use one of several possible directives, but I won't always know which one. If I set that directive name in the controller's scope, how can I use it within the template of the main directive?

Here's a plnkr with what's below.

HTML:

<div ng-app="music" ng-controller="rock">
  <h1>Favorite Bands</h1>
  <prog></prog>
</div>

JS:

angular.module('music', []);

angular.module('music').controller('rock', ['$scope', function($scope){
  $scope.directiveName = 'dinosaurs';
  $scope.bands = [
    { name:'Rush'}, { name:'King Crimson' }, { name: 'Porcupine Tree'}, { name: 'Marillion'}];
}]);

angular.module('music').directive('prog', function(){
  return {
    restrict: 'E',
    replace: true,
    template: '<ul><li ng-repeat="band in bands"><{{directiveName}}>{{band.name}}</{{directiveName}}></li></ul>'
  };
});

angular.module('music').directive('dinosaurs', function(){
  return {
    restrict: 'E',
    transclude: true,
    template: '<ng-transclude></ngtransclude> - DINOSAUR!'
  };
});

In this case, I'm setting the $scope.directiveName to dinosaurs, which is the name of the directive I want to use inside the main one, called prog.

In prog's template, I'm trying to use interpolation to insert the name of the directive into the brackets. That, however, outputs this:

  • <dinosaurs>Rush
  • <dinosaurs>King Crimson
  • <dinosaurs>Porcupine Tree
  • <dinosaurs>Marillion

I've also tried using class name on a span: , and that inserts "dinosaurs" into the span as a class but Angular doesn't then process it as a directive.

I'm not sure if I need an isolate scope here, but from what I've read I don't think that's relevant. I'm also new to transclusion, but I think that the dinosaurs directive should take the content of each list item and add " - DINOSAURS!" to the end of it.

What's the best practice way to go about passing in the name of one directive to another?

Scott McD.
  • 129
  • 1
  • 13
  • I've been looking at https://docs.angularjs.org/guide/directive and [Dan Wahlin's posts on directives](http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-2-isolate-scope). Several topics, like [this one](http://stackoverflow.com/questions/16787276/use-an-angular-directive-inside-another-directive) address directives inside other directives, but not passing in the names of those directives. – Scott McD. Nov 07 '14 at 20:13
  • if you declare the dinosaurs directive first, does that help? – dandavis Nov 07 '14 at 20:26
  • @dandavis - Nice thought, but apparently not. I tried it on the plnkr, but no luck. – Scott McD. Nov 07 '14 at 20:37
  • you can use the link function to connect to the parent directive controller - `function(scope, el, attr, ctrl)` – Dylan Nov 07 '14 at 20:41

3 Answers3

1

what I would do to avoid the messy $compiles is use ng-include as the switch, something like this:

angular.module('music').directive('prog', function(){
  return {
    restrict: 'E',
    replace: true,
    template: '<ul><li ng-repeat="band in bands"><div ng-include="directiveName + \'.html\'"></div></li></ul>'
  };
});
yggie
  • 297
  • 3
  • 8
  • Definitely promising so far - I've gotten it to work in principle and just have to work out a few syntax things. Thanks! – Scott McD. Nov 07 '14 at 21:37
1

Update - You have a few options within the directive to change the template:

The easiest first idea, is template:function(el, attr) which allows you return a function enabling you to change the template based on non-interpolated attributes. so it probably doesn't meet your needs.

template:function() plunker,

The other way is to $compile the templates and replace you element in the link function.

$compile plunker


This wasn't about multi-template -

You can reach the controller of the parent directive in the link function and maintain scope in it. here's attempt to cut it down to the essentials:

app.directive("parentDirective", function() {
  return {
    restrict: 'EA',
    controller: function($scope) {
        this.callFunc = function(){
           ...
        }
    }
  }
});

app.directive("childDirective", function($compile, $log) {

  return {
    require: '?^parentDirective',
    scope: {
       model: '=ngModel'
    },
    link : function(scope, el, attr, ctrl) {
        ctrl.callFunc();
    }
...

and a similar plunker or plunker

Dylan
  • 4,703
  • 1
  • 20
  • 23
  • Sorry if I'm missing something obvious, but how does that help? I don't have a controller on the parent at the moment, though maybe I need one. I'm looking for ways to identify the childDirective in the parentDirective. If the callFunc() would take care of that, how would the template make use of it? Thanks for the response! – Scott McD. Nov 07 '14 at 21:33
  • Maybe I misunderstood, because best-practice for a directive is to be declarative, so ` isn't exactly declaring a whole lot, and I would say to add your child elements to the html, but - it looks more like your intention is to use multiple directives to alternate templates by $scope.variable in the main controller? – Dylan Nov 07 '14 at 22:02
  • Yeah, isn't all that declarative - a victim of simplification I guess. The actual case is that I'm using a tree-grid directive that accepts an object with the data and hierarchy. In some cases a column in the grid should be read-only with the value, and in other cases it should have a control to change the value. That control to change the value is what I'm trying to insert as a directive. – Scott McD. Nov 07 '14 at 22:41
  • The template function might work if I use a configuration object that I can also access without interpolation. I'm still learning about isolate scopes and the compile and link phases, and your simple examples help quite a bit. Many thanks for the update. – Scott McD. Nov 08 '14 at 03:05
1

One possible solution is to shift the responsibility of "how do I render this item" from the controller/directive into the item itself.

The simplest way of doing this would be to give the item a templateUrl property. You could then introduce a simple wrapper directive to bind the items:

myModule.directive('bandView', function() {
  return {
    scope: {band: '=bandView'}
    templateUrl: '<div ng-include="band.templateUrl"></div>'
  };
});

Then, if you have some list of bands, you can render them:

<h1>My band list</h1>
<div ng-repeat="b in bands" band-view="b"></div>

Then you would have different html templates for each type of rendering you want to do.

Here's the idea running: http://jsbin.com/EhAnIMaJ/2/edit?html,js,output

Jeremy Elbourn
  • 2,630
  • 1
  • 18
  • 15
  • Thanks for the answer. That TemplateCache thing is pretty cool - I didn't know about that. If I understand correctly, this wouldn't have nested directives at all, just the one that does different includes based on the url property. Makes sense. – Scott McD. Nov 08 '14 at 02:33
  • Yes- the use of $templateCache in my example is just there to demonstrate using different template files inside jsbin. In a real app, you would most likely want separate template files. – Jeremy Elbourn Nov 11 '14 at 20:26