Jason
Jason

Reputation: 2617

How to pass transclusion down through nested directives in Angular?

I am trying to figure out how to pass a transclusion down through nested directives and bind to data in the inner-most directive. Think of it like a list type control where you bind it to a list of data and the transclusion is the template you want to use to display the data. Here's a basic example bound to just a single value (here's a plunk for it).

html

<body ng-app="myApp" ng-controller="AppCtrl as app">
    <outer model="app.data"><div>{{ source.name }}</div></outer>
</body>

javascript

angular.module('myApp', [])

.controller('AppCtrl', [function() {
    var ctrl = this;

    ctrl.data = { name: "Han Solo" };

    ctrl.welcomeMessage = 'Welcome to Angular';
}])

.directive('outer', function(){
    return {
        restrict: 'E',
        transclude: true,
        scope: {
            model: '='
        },
        template: '<div class="outer"><inner my-data="model"><div ng-transclude></div></div></div>'
    };
})

.directive('inner', function(){
    return {
        restrict: 'E',
        transclude: true,
        scope: {
            source: '=myData'
        },
        template :'<div class="inner" my-transclude></div>'
    };
})

.directive('myTransclude', function() {
    return {
        restrict: 'A',
        transclude: 'element',
        link: function(scope, element, attrs, controller, transclude) {
            transclude(scope, function(clone) {
                element.after(clone);
            })
        }
    }
});

As you can see, the transcluded bit doesn't appear. Any thoughts?

Upvotes: 2

Views: 847

Answers (3)

lenilsondc
lenilsondc

Reputation: 9800

In this case you don't have to use a custom transclude directive or any trick. The problem I found with your code is that transclude is being compiled to the parent scope by default. So, you can fix that by implementing the compile phase of your directive (this happens before the link phase). The implementation would look like the code below:

app.directive('inner', function () {
    return {
        restrict: 'E',
        transclude: true,
        scope: {
            source: '=myData'
        },
        template: '<div class="inner" ng-transclude></div>',
        compile: function (tElem, tAttrs, transclude) {
            return function (scope, elem, attrs) { // link

                transclude(scope, function (clone) {
                    elem.children('.inner').append(clone);
                });
            };
        }
    };
});

By doing this, you are forcing your directive to transclude for its isolated scope.

Upvotes: 3

Jason
Jason

Reputation: 2617

Thanks to Zach's answer, I found a different way to solve my issue. I've now put the template in a separate file and passed it's url down through the scopes and then inserting it with ng-include. Here's a Plunk of the solution.

html:

<body ng-app="myApp" ng-controller="AppCtrl as app">
    <outer model="app.data" row-template-url="template.html"></outer>
</body>

template:

<div>{{ source.name }}</div>

javascript:

angular.module('myApp', [])

.controller('AppCtrl', [function() {
    var ctrl = this;

    ctrl.data = { name: "Han Solo" };
}])

.directive('outer', function(){
    return {
        restrict: 'E',
        scope: {
            model: '=',
            rowTemplateUrl: '@'
        },
        template: '<div class="outer"><inner my-data="model" row-template-url="{{ rowTemplateUrl }}"></inner></div>'
    };
})

.directive('inner', function(){
    return {
        restrict: 'E',
        scope: {
            source: '=myData',
            rowTemplateUrl: '@'
        },
        template :'<div class="inner" ng-include="rowTemplateUrl"></div>'
    };
});

Upvotes: 2

Zach
Zach

Reputation: 3207

You can pass your transclude all the way down to the third directive, but the problem I see is with the scope override. You want the {{ source.name }} to come from the inner directive, but by the time it compiles this in the first directive:

template: '<div class="outer"><inner my-data="model"><div ng-transclude></div></div></div>'

the {{ source.name }} has already been compiled using the outer's scope. The only way I can see this working the way you want is to manually do it with $compile... but maybe someone smarter than me can think of another way.

Demo Plunker

Upvotes: 1

Related Questions