javascriptangularjsangularjs-directiveangularjs-ng-transclude

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?


Solution

  • 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.