Render a directive inside another directive (within repeater template)

All we need is an easy explanation of the problem, so here it is.

I am trying to render a directive inside another directive (not sure if the repeater inside the template is working this), and it seems to just output as text rather than compiling the directive (plunker code here: http://plnkr.co/edit/IRsNK9)

Any ideas on how I can actually get it to render properly my-dir-one, my-dir-two, my-dir-three directives inside the repeater?

index.html

<!doctype html>
<html ng-app="plunker" >
<head>
  <meta charset="utf-8">
  <title>AngularJS Plunker</title>
  <link rel="stylesheet" href="style.css" rel="nofollow noreferrer noopener">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js"></script>
  <script src="app.js"></script>
  <script id="partials/addressform.html" type="text/ng-template">
      partial of type {{type}}<br>
  </script>
</head>
<body>
  <div container></div>

  <br /><br /><br />
  <b>Below is just to test the directives are actually usable outside the repeater</b>
  <div my-dir-one></div>
  <div my-dir-two></div>
  <div my-dir-three></div>
</body>
</html>

app.js

var app = angular.module('plunker', []);

app.directive('container', function () {

  return {
        restrict: 'A',
        scope: {},
        replace: true,

        template: '<div class="views">' +
                  '    <div class="view" ng-repeat="view in views">' +
                  '        <div {{view.dir}}>{{view.dir}}</div>' +
                  '    </div>' +
                  '</div>',

        link: function (scope, elm) {

            scope.views = [
        { dir: 'my-dir-one' },
        { dir: 'my-dir-two' },
        { dir: 'my-dir-three' }
      ];
        }
    }
});

app.directive('myDirOne', function () {
  return {
    restrict: 'A',
    scope: {},
    replace: true,
    template: '<div>This is directive one.</div>'
  }
});

app.directive('myDirTwo', function () {
  return {
    restrict: 'A',
    scope: {},
    replace: true,
    template: '<div>This is directive two.</div>'
  }
});

app.directive('myDirThree', function () {
  return {
    restrict: 'A',
    scope: {},
    replace: true,
    template: '<div>This is directive three.</div>'
  }
});

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I managed to work around this issue by re-writing the code:

First I updated the template code as follows:

template: '<div class="views">' +
          '    <div class="view-wrapper" ng-repeat="view in views">' +
          '        <div view="{{view.dir}}"></div>' +
          '    </div>' +
          '</div>',

Note that I created a new ‘view’ directive. Next the view directive definition as follows:

app.directive('view', ['$compile', function (compile) {

    return {
        restrict: 'A',
        scope: {
            view: '@'
        },
        replace: true,   
        template: '<div class="view"></div>',

        controller: ['$scope', function (scope) {
            scope.$watch('view', function (value) {
                scope.buildView(value);
            });
        }],

        link: function (scope, elm, attrs) {

            scope.buildView = function (viewName) {
                var view = compile('<div ' + viewName + '></div>')(scope);
                elm.append(view);
            }
        }
    }
}]);

So essentially, the view.dir variable is passed as an attribute to the ‘view’ directive, which then watches it’s value and compiles a template with the directive in it.

Method 2

This is in part a timing problem…I think that by the time it’s resolving the {{}} expressions, it’s already parsed out and rendered directives. It’s not the nesting or the repeater that are the problem, per se.

What you’re after here, though, is ‘decide which directive to render based on the value of a variable’. There are a couple ways to do that.

Here’s one that should work, though it might not scale as nicely as you’d like:

<div class='views' ng-repeat='view in views'>
  <div ng-switch='view.dir'>
    <div ng-when='my-dir-one' my-dir-one />
    <div ng-when='my-dir-two' my-dir-two />
    <div ng-when='my-dire-three' my-dir-three />
  </div>
</div>

Other options for similar tricks: it looks like you could use ngBindTemplate to take a string from your data and use it as the template for an element. This would probably permit some tricky (and illegible) behavior.

You can specify a directive for an element as a class, but I don’t know whether using ngClass to do this would allow you to dynamically select the directive, or whether that would come too late in the pipeline.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply