Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapping the angular-ui tabset directive and encountering the "Multiple directives asking for transclusion/ isolated scope" errors

I'm trying to extend the angular-ui tabset functionality and I'm running into issues with wrapping it.

This plunker is the tabset directive un-wrapped:

http://plnkr.co/edit/AhG3WVNxCal5fZOUbSu6?p=preview

This plunker contains my first attempt at wrapping the tabset directive:

http://plnkr.co/edit/naKXbeVOS8nizwDPUrkT?p=preview

The initial wrapping approach is straight-forward wrapping. But... I introduce extra divs in the replacement template to avoid the "Multiple directives asking for isolated scope" and "Multiple directives asking for transclusion" angular errors and to make sure transclusion happens.

Key code snippets:

.directive('urlTabset', function() {
  return {
    restrict: 'E',
    transclude: true,
    replace: true,
    scope: {
      tabManager: '='
    },
    controller: [ "$scope", function($scope) {
      var tabManager = $scope.tabManager;
    }],
    template:
      '<div>' +
        '<tabset>' +
          '<div ng-transclude>' +
          '</div>' +
        '</tabset>' +
      '</div>'
  };
})

.directive('urlTab', function() {
  return {
    require: '^urlTabset',
    restrict: 'E',
    transclude: true,
    replace: true,
    scope: { tabName: '@' },
    link: function(scope, element, attrs, urlTabsetCtrl) {
    },
    template:
      '<div>' +
        '<tab>' +
          '<div ng-transclude>' +
          '</div>' +                  
        '</tab>' +
      '</div>'
  };
});

However, I think the extra divs in the template are causing issues. Here is the unwrapped tabset with extra divs in the places my template would add them.

http://plnkr.co/edit/kjDs7xJcZqltCAqUSAmX?p=preview

So the logical thing is to eliminate the divs... but this is where I need the help. Does anyone know of a clean way to do this without hitting the "Multiple directives asking for isolated scope" and "Multiple directives asking for transclusion" angular errors. Here is one failed attempt.

http://plnkr.co/edit/0C6lFNhfdTVcF7ahuN3G?p=preview

Error: Multiple directives [urlTab, tab] asking for transclusion on: <tab class="ng-isolate-scope ng-scope">

BTW, in case you're wondering what I'm trying to do, my end goal is to use the tabManager attribute passed to urlTabset to auto-populate fields in the tab directive (wrapped by urlTab). To be more concrete this is what I'm aiming for:

.directive('urlTab', function() {
  return {
    require: '^urlTabset',
    restrict: 'E',
    transclude: true,
    replace: true,
    scope: { tabName: '@' },
    link: function(scope, element, attrs, urlTabsetCtrl) {
      scope.tabs = urlTabsetCtrl.tabs;
      scope.tabSelected = urlTabsetCtrl.tabSelected;
    },
    template:
      '<tab active="tabs[tabName].active" disabled="tabs[tabName].disabled" select="tabSelected(tabName)" ng-transclude>' +
      '</tab>'
  };
});

The template above obviously does not work, but it gives you the gist of what I'm trying to do.

And I'm okay with a solution that requires the wrapping directive not have an isolated scope. I can get around this by storing state in the controller context.

like image 663
Amir Avatar asked Nov 23 '13 08:11

Amir


1 Answers

If you are trying to augment angular-ui's functionality, you may be better off doing it with attribute directives rather than brand new elements. I may be mistaken but it looks like you're not intending to alter the general stucture of the DOM other than to replace your directive with angular-ui's ones. For instance, using the HTML

<tabset url-tabset>
    <tab url-tab>
        <tab-heading>
            <i class="icon-list"></i> Details
        </tab-heading>
        Details content.
    </tab>
    <tab url-tab>
        <tab-heading>
            <i class="icon-thumbs-up"></i> Impact
        </tab-heading>
        Impact tab content.
    </tab>                    
</tabset>

would mean you no longer need to perform any transclusion or template replacement. This would avoid that problem all together.

This leaves the problem of isolated scope for attributes you want to use for the augmentation. Instead of using this, you can use scope: true to grab the same isolated scope as tab and tabset (though you cannot define bindings here) and you can get attributes as you would use normal bound values by using $parse and attrs.

Your directives (with the functionality from your second plunker) then end up looking something like this.

angular.module('plunker', ['ui.bootstrap'])

.directive('urlTabset', function() {
  return {
    restrict: 'A',
    require: 'tabset', // Confirm the directive is only being used on tabsets
    controller: [ "$scope", "$attrs", function($scope, $attrs) {
      var tabManagerGetter = $parse($attrs.tabManager); // '='
      this.getTabManager = function() {
        return tabManagerGetter($scope);
      };

      // fun stuff here
    }]
  };
})

.directive('urlTab', function() {
  return {
    require: ['tab', '^urlTabset'],
    restrict: 'A',
    link: function(scope, element, attrs, ctrls) {
      var urlTabsetCtrl = ctrls[1];

      function getTabName() {
        return attrs.tabName; // '@'
      }

      var tabManager = urlTabsetCtrl.getTabManager();

      // fun stuff here
    }
  };
});
like image 128
Andyrooger Avatar answered Sep 20 '22 06:09

Andyrooger