5
votes

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.

1
It's pretty logic i think your outer scope is isolate so how you can herit of it ? - Thomas Pons
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
Ah the ng-transclude set the ng-scope to false ! - Thomas Pons

1 Answers

9
votes

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.