Jhecht
Jhecht

Reputation: 4435

Accessing component controller from transcluded HTML

Plunker, because the snippet editor isn't liking me today.

Quick Info

I'm working on using .component() in place of .directive() when using Angular 1.6 to get myself more into the type of design pattern used by Angular 2. The issue is that I cannot use any references to either $tabs or $tab (the controllers for those respective components). Nothing is output by using either {{$tab.tabsCtrl.nothing}} or {{$tabs.nothing}}.

Please Note:this is not my actual scenario, but it does share a common problem with what I am actually doing.

I've searched around and I get a lot of results for the Angular 2 components, but if I am being honest reading through it is basically Greek.

Code Reference(not working in snippet editor, only for reference)

// Code goes here
angular.module('main.app', [])
  .component('tabs', {
    controller: function($http) {
      this.tabs = [];
      this.nothing = 'nada';
      this.addTab = function(tab) {
        this.tabs.push(tab);
      }; //end addTab
      this.selectTab = function(tab) {
        this.tabs.map(function(item) {
          item.selected = false;
        });
        var selected = this.tabs.filter(function(item) {

          return item === tab;
        });
        if (selected.length) selected[0].selected = true;
      }; //End selectTab
    },
    template: '<ul class="nav nav-tabs nav-justify justify-content-center"><li class="nav-item" ng-repeat="tab in $tabs.tabs" ><a href="#" ng-click="$tabs.selectTab(tab)" class="nav-link" ng-class="{\'active\':tab.selected}">{{tab.tabTitle}}</a></li></ul><div class="tabs-content" ng-transclude></div>',
    transclude: true,
    controllerAs: '$tabs'
  })
  .component('tab', {
    require: {
      'tabsCtrl': '^tabs'
    },
    bindings: {
      'tabTitle': '@',
      'selected': '<'
    },
    controller: function() {
      this.$onInit = function() {
        this.selected = this.selected || false;
        this.tabsCtrl.addTab(this);
      }; //end $onInit
    },
    transclude: true,
    controllerAs: '$tab',
    template: '<div class="tab" ng-show="$tab.selected"><div ng-transclude></div></div>'
  })
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>
<tabs>
  <tab tab-title="First Tab" selected="true">
    <div class="jumbotron">
      <h3>Lorem ipsum.</h3>
      <p class="lead">Lorem ipsum dolor sit amet.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iusto, in!</p>
    </div>
    some other text
  </tab>
  <tab tab-title="Second Tab">
    <strong>With a header!</strong>
    <div class="row">
      <div class="col-6 bg-primary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nisi, quam, quod, optio qui cum rerum vel eos rem aspernatur quia maxime incidunt numquam ipsum eum neque dicta distinctio. Minus, itaque.</div>
      <div class="col-6 bg-danger">Reprehenderit, numquam, rerum, reiciendis neque adipisci provident ea quo illo praesentium inventore fuga quisquam ducimus? Ipsum, autem, illo ullam corporis incidunt ad labore accusantium tempora officia quas quia eaque facere.</div>
    </div>
  </tab>
  <tab tab-title="Potato Tab">
    <h3>Big books {{$tabs.nothing||'nah'}}</h3>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iste quos assumenda vero fugiat officia pariatur consequatur deserunt quisquam veniam nemo?</p>
    <p>Fugiat, error, impedit, accusantium consequuntur beatae facere esse voluptatum enim animi porro commodi modi cupiditate aliquam iure ipsa. A, officiis!</p>
    <p>Architecto velit quod explicabo laborum reprehenderit culpa tempora facilis minima eum. Natus aliquid eaque laboriosam accusamus dolor hic similique ad.</p>
  </tab>
</tabs>

Thanks, just in case I forget to say it in the future!

Upvotes: 0

Views: 318

Answers (1)

tasseKATT
tasseKATT

Reputation: 38490

The transcluded scope will be a child scope of the directive's isolate scope.

Since components use isolate scopes, you can't make use of prototypal inheritance by just doing {{$tabs.nothing}}. It would work if $tabs was exposed via $rootScope however, or any other non-isolate scope above tabs in the hierarchy (only ng-app in your example, so only $rootScope).

You can walk the scope chain manually.

Based on your example:

  1. The current scope would be the transcluded child scope of the tab directive
  2. The first $parent would be the isolate tab scope
  3. The second $parent would be the transcluded child scope of the tabs directive
  4. The third $parent would be the isolate tab scope

This gives:

{{$parent.$parent.$parent.$tabs.nothing}}

Demo: http://plnkr.co/edit/jpTGVKWKEQlmGYpaeqtZ?p=preview

In most cases this isn't really a feasible solution. Probably better to expose the functionality that the transcluded content needs via a service.

Hard to give a better solution without knowing the real use case.

Upvotes: 1

Related Questions