1

I have a tab control which enables content within the control to be shown or hidden based on the selected tab.

This works well enough, but the content of the tab panes starts behaving oddly with the scope. Referencing the scope within the transcluded content doesn't seem to refer to the defined controller scope any more.

This can be seen with any directive which has transcluded content. Given the specific controller:

app.controller("MainCtrl", function ($scope) {
  $scope.getTestText = function () {
    alert($scope.testText);
    alert(this.testText);
  }
});

Which is used in the following markup:

<h1>Outside Tab</h1>
<div>
    <input type="text" ng-model="testText" />
    <button ng-click="testText = 'outside'">Set Test Text</button>
    <button ng-click="getTestText()">Get Test Text</button>
    {{testText}}
</div>

<simple-directive>
  <h1>Inside Directive</h1>
  <div>
      <input type="text" ng-model="testText" />
      <button ng-click="testText = 'inside'">Set Test Text</button>
      <button ng-click="getTestText()">Get Test Text</button>
      {{testText}}
  </div>
</simple-directive>

See this plunker.

If you click the top "Set Test Text" button then the "Get Test Text" button, the two alerts show the same thing ("outside"). If you click the second "Set Test Text" button then "Get Test Text", there is a different result: although "this" has the expected "inside" value, the value on the scope is still "outside".

One workaround is to define the controller inside the transcluded content, like so:

<simple-directive>
  <h1>Inside Directive</h1>
  <div ng-controller="MainCtrl">
      <input type="text" ng-model="testText" />
      <button ng-click="testText = 'inside'">Set Test Text</button>
      <button ng-click="getTestText()">Get Test Text</button>
      {{testText}}
  </div>
</simple-directive>

But I'd rather avoid this if possible.

So my question: is there a better way of doing this that doesn't change the scope? Is this just expected behaviour?

Carl Sharman
  • 4,435
  • 1
  • 30
  • 29

1 Answers1

2

ngTransclude creates a child (non-isolated) scope :

enter image description here

The $transclude function creates a new child scope by default, source code:

transcludedScope = scope.$new();

The best solution is to avoid referring to primitives on the scope:

Why not use primitives? see similar answers...

A demo plunker: http://plnkr.co/edit/LuU6ard1JYsYCOvlP5iY?p=preview

app.controller("MainCtrl", function ($scope) {
  $scope.test = {};
  $scope.getTestText = function () {
    alert($scope.test.text);
    alert(this.test.text);
  }
});

template:

<input type="text" ng-model="test.text" />
<button ng-click="test.text = 'inside'">Set Test Text</button>
<button ng-click="getTestText()">Get Test Text</button>
{{test.text}}

Another solution is to manually transclude without creating a new child scope:

app.directive('simpleDirective', function () {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: {},
        link: function(scope,elm,attrs,ctrl,$transclude){
          $transclude(scope.$parent, function(clone){
            elm.after(clone);
          })
        }
    };
})
Community
  • 1
  • 1
Ilan Frumer
  • 32,059
  • 8
  • 70
  • 84