Search code examples
angularjsangularjs-directiveangularjs-scopeangular-materialangularjs-ng-transclude

Wrapping md-tabs in a directive gives an "Orphan ngTransclude Directive" error


I'd like to create a directive that wraps md-tabs, but I'm getting an error, "Orphan ngTransclude Directive". I've replicated the error in this snippet:

angular.module('transcludeExample', ['ngMaterial'])
   .directive('worksGreat', function(){
      return {
        restrict: 'E',
        transclude: true,
        template: '<ng-transclude></ng-transclude>'
      };
  })
  .directive('doesntWork', function(){
      return {
        restrict: 'E',
        transclude: true,
        template: '' + 
         '<md-tabs md-dynamic-height>' +
           '<md-tab label=\'tab 1\'>' + 
             '<ng-transclude></ng-transclude>' +
           '</md-tab>' +
         '</md-tabs>'
      };
  })
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Example - example-simpleTranscludeExample-production</title>
  
  <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/0.11.2/angular-material.min.css">
</head>
<body ng-app="transcludeExample">
  <!-- Angular Material Dependencies -->
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-aria.js"></script>

    <!-- Angular Material Javascript using GitCDN to load directly from `bower-material/master` -->
    <script src="https://gitcdn.link/repo/angular/bower-material/master/angular-material.js"></script>

<div>
  <h3>ng-transclude in a directive works great:</h3>
  <works-great>Inner text</works-great>
  <hr/>
  
  <h3>md-tabs without a directive works great:</h3>
  <md-tabs md-dynamic-height>
    <md-tab label="tab 1">
      Inner text
    </md-tab>
  </md-tabs>
  <hr/>
  
  <h3>combining md-tabs with a directive doesn't work:</h3>
  <doesnt-work>Inner text</doesnt-work>
</div>
</body>
</html>

I found this answer that gets into manually manipulating elements outside of the template, but I'm hoping for a cleaner "more angular" way. What's going on here? Is there a way I can define what directive the ng-transclude should apply to?


Solution

  • This comment on github presents the solution. ng-transclude is designed to be generic so that it can work with any directive, but in this case, that's where the problem comes from. Fortunately, it's extremely simple to mimic, and we can specify what parent it should apply to with require.

    I've updated my code snippet with a working solution:

    var orphanDemoCtrl = function($transclude){
      this.$transclude = $transclude;
    }
    
    angular.module('transcludeExample', ['ngMaterial'])
      .controller('orphanDemoCtrl', orphanDemoCtrl)
      .directive('orphanDemo', function(){
          return {
            restrict: 'E',
            transclude: true,
            template: '' + 
             '<md-tabs md-dynamic-height>' +
               '<md-tab label=\'tab 1\'>' + 
                 '<orphan-demo-transclude></orphan-demo-transclude>' +
               '</md-tab>' +
             '</md-tabs>',
            controller: orphanDemoCtrl
          };
      })
      .directive('orphanDemoTransclude', function(){
          return {
            require: "^orphanDemo",
            link: function($scope, $element, $attrs, orphanDemoCtrl){
              orphanDemoCtrl.$transclude(function(clone) {
                $element.empty();
                $element.append(clone);
              });
            }
         }
      })
    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Example - example-simpleTranscludeExample-production</title>
      
      <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/0.11.2/angular-material.min.css">
    </head>
    <body ng-app="transcludeExample">
      <!-- Angular Material Dependencies -->
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-aria.js"></script>
    
        <!-- Angular Material Javascript using GitCDN to load directly from `bower-material/master` -->
        <script src="https://gitcdn.link/repo/angular/bower-material/master/angular-material.js"></script>
    
    <div>  
      <h3>combining md-tabs with a directive works now:</h3>
      <orphan-demo>Inner text</orphan-demo>
    </div>
    </body>
    </html>