Transclusion and scopes in angular: Why is this not working?

I have a very simple setup:

<pane title="mytitle">Title in parent (transcluded): {{title}}</pane>

and

angular.module('transclude', [])
 .directive('pane', function(){
    return {
      restrict: 'E',
      transclude: true,
      scope: { title:'@' },
      template: '<div>' +
                  '<div>Title in isolated scope: {{title}}</div>' +
                  '<div ng-transclude></div>' +
                '</div>'
    };
});

The plunker is here: http://plnkr.co/edit/yRHcNGjQAq1NHDTuwXku

The transclusion itself is working but the {{title}} only gets replaced in the directive's template.
The {{title}} inside the transcluded element however stays empty even though the directive has a variable title in its scope. Why is that?

Answers:

Answer

The scope of the transcluded element is not a child scope of the directive but a sibling one. This is what documentation says:

In a typical setup the widget creates an isolate scope, but the transclusion is not a child, but a sibling of the isolate scope.

The simplest solution in this case how you can access transcuded scope is like this:

.directive('pane', function () {
    return {
        restrict: 'E',
        transclude: true,
        scope: {
            title: '@'
        },
        template:
            '<div>' +
                '<div>Title in isolated scope: {{title}}</div>' +
                '<div ng-transclude></div>' +
            '</div>',
        link: function (scope, element, attrs) {
            scope.$$nextSibling.title = attrs.title;
        }
    };
});

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

Answer

@dfsq is correct about:

The scope of the transcluded element is not a child scope of the directive but a sibling one

I'd like to add more comments why this is the expected behavior of angularJs. As the docs says:

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.

The transcluded content inside the directive is arbitrary, it should not have knowledge about the directive's isolate scope, otherwise, we would create a tight coupling code. Because for the transcluded content to work, the content must know the implementation details of your directive (what is available to use).

If you decide that the content belongs to the directive. You have 2 options:

1) Make the content part of the template

    angular.module('transclude', [])
         .directive('pane', function(){
            return {
              restrict: 'E',
              transclude: true,
              scope: { title:'@' },
              template: '<div>' +
                          '<div>Title in isolated scope: {{title}}</div>' +
                          '<div>' +
                             ' Title in parent (transcluded): {{title}} ' +
                         ' </div>' +
                        '</div>'
            };
   });

DEMO

2) Use custom tranclusion to bind the scope yourself:

angular.module('transclude', [])
     .directive('pane', function(){
        return {
          restrict: 'E',
          transclude: true,
          scope: { title:'@' },
          template: '<div>' +
                      '<div>Title in isolated scope: {{title}}</div>' +
                      '<div class="transclude"></div>' +
                    '</div>',
        link: function (scope, element, attr,controller, linker) {
           linker(scope, function(clone){
                  element.find(".transclude").append(clone); // add to DOM
           });
          }
        };
    });

DEMO

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.