Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested directives and parent scopes

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.

like image 445
Mike Richardson Avatar asked Sep 26 '13 11:09

Mike Richardson


1 Answers

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.

like image 152
Thomas Pons Avatar answered Nov 05 '22 14:11

Thomas Pons