5

The problem I have can be see at http://jsfiddle.net/miketheanimal/2CcYp/13/ This strips down my problem to a minimum.

I have a controller "main", a directive "outer" which transclude's, and a directive "inner" which doesn't. Each directive has an isolate scope and a controller. The main and the directive controllers set $scope._name = '...' so I can tell them apart.

var module = angular.module('miketa', []);
function main ($scope) {
    $scope._name = 'main' ;
} ;
module.directive('outer', function() {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: {},
        template: '<div><div ng-transclude></div></div>',
        controller: [ '$scope', function($scope) {
            $scope._name = 'outer' ;
            document.getElementById('opn').innerHTML = $scope.$parent._name ;
        }]}});
module.directive('inner', function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {},
        template: '<div></div>',
        controller: [ '$scope', function($scope) {
            $scope._name = 'inner' ;
            document.getElementById('ipn').innerHTML = $scope.$parent._name ;
        }]}});

The HTML nests these as main -> outer -> inner. The controller functions in the directives copy their parent scope name (ie., *$scope.$parent._name) into the rendered HTML (apologies for manipulating the DOM directly, it was the easiest way to display the names!).

I would expect outer to show the name from the controller (ie., "main") which is does, and I'd expect inner to show the name from outer (ie., "outer"), which is doesn't, rather it shows "main" as well.

The problem actually manifests itself since in the real code, I'd like to bind between the inner and outer scopes, but inner ends up binding to the main scope.

Murat Çorlu
  • 8,207
  • 5
  • 53
  • 78
Mike Richardson
  • 292
  • 3
  • 12
  • It's pretty logic i think your outer scope is isolate so how you can herit of it ? – Thomas Pons Sep 26 '13 at 11:26
  • Yes, the scopes are isolate, so they don't prototypically inherit, but they do have parent scopes (the $scope.$parent bit), and these don't seem to work the way I expect (inner -> outer -> main). As I note in the last paragraph, this becomes a problem when I try to bind between the scopes of *inner* and *outer* (ie., in the inner *scope: { innervar: '=outervar'}*). – Mike Richardson Sep 26 '13 at 11:31
  • Ah the ng-transclude set the ng-scope to false ! – Thomas Pons Sep 26 '13 at 11:39

1 Answers1

9

In fact it's not a bug, it's the desired behavior. From the docs on the $compile service:

In a typical setup the widget creates an isolate scope, but the transclusion is not a child, but a sibling of the isolate scope. This makes it possible for the widget to have private state, and the transclusion to be bound to the parent (pre-isolate) scope.

See also: Why ng-transclude's scope is not a child of its directive's scope - if the directive has an isolated scope?

If you really need to get it work forget ng-transclude and do:

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

function main($scope) {
    $scope._name = 'main';
};
module.directive('outer', function () {
    return {
        restrict: 'E',
        replace: true,
        scope: {},
        template: '<div><inner></inner></div>',
        controller: ['$scope', function ($scope) {
            $scope._name = 'outer';
            document.getElementById('opn').innerHTML = $scope.$parent._name;
        }]
    }
});
module.directive('inner', function () {
    return {
        restrict: 'E',
        replace: true,
        scope: {},
        template: '<div></div>',
        controller: ['$scope', function ($scope) {
            $scope._name = 'inner';
            document.getElementById('ipn').innerHTML = $scope.$parent._name;
        }]
    }
});

And voila! It works.

Community
  • 1
  • 1
Thomas Pons
  • 7,709
  • 3
  • 37
  • 55
  • I'd wondered if the transclude had something to do with it, I'd found some other sort-of-but-not-quite-related posts. But your change rather makes *outer* pointless, I might as well roll it into *inner*. What I'm trying to do is have a directive *outer* that can wrap different content (ie., wrap *inner1* and *inner2* and so on). Is there any way I can do that? – Mike Richardson Sep 26 '13 at 12:15
  • No any ng-trnasclude isolate the scope u have to accept that ! It's the logic of the transclusion in AngularJSn if you read the link thread and the docs – Thomas Pons Sep 26 '13 at 12:17
  • Lol good luck :) ! Directive API is the most powerful part of AngularJS but hard as hell ! Anyway maybe you have to accept or delete the question i don't know – Thomas Pons Sep 26 '13 at 12:51