Code


<!DOCTYPE html> <html> <head> <script src="angular.js"></script> <script src="script.js"></script> </head> <body ng-app="mainModule"> <div ng-controller="mainController"> <h3>Base scope</h3> <label>Scope variable: <input type="text" ng-model="scopeVar"/></label><br /> <br /> <strong>Scope ID:</strong> {{$id}}<br /> <strong>Scope variable:</strong> {{scopeVar}}<br /> <br /> <h3>1. Transclude: true</h3> <ngh-transclude-true style="background-color: lightblue;"> <h4>[Green is the transcluded content]</h4> <label>Scope variable: <input type="text" ng-model="scopeVar"/></label><br /> <br /> <strong>Scope ID:</strong> {{$id}}<br /> <strong>Parent scope ID:</strong> {{$parent.$id}}<br /> <strong>Scope variable:</strong> {{scopeVar}}<br /> </ngh-transclude-true> <br /> <h3>2. Transclude: 'element'</h3> <h3>2.1. Pre-bound scope</h3> <div ngh-transclude-element="1" style="background-color: lightyellow;"> <h4>[Yellow is the transcluded content]</h4> <label>Scope variable: <input type="text" ng-model="scopeVar"/></label><br /> <br /> <strong>Scope ID:</strong> {{$id}}<br /> <strong>Parent scope ID:</strong> {{$parent.$id}}<br /> <strong>Scope variable:</strong> {{scopeVar}}<br /> </div> <br /> <h3>2.2. Clone element with pre-bound scope</h3> <div ngh-transclude-element="2" style="background-color: lightyellow;"> <h4>[Yellow is the transcluded content]</h4> <label>Scope variable: <input type="text" ng-model="scopeVar"/></label><br /> <br /> <strong>Scope ID:</strong> {{$id}}<br /> <strong>Parent scope ID:</strong> {{$parent.$id}}<br /> <strong>Scope variable:</strong> {{scopeVar}}<br /> </div> <br /> <h3>2.3. Clone element and assign directive's scope</h3> <div ngh-transclude-element="3" style="background-color: lightyellow;"> <h4>[Yellow is the transcluded content]</h4> <label>Scope variable: <input type="text" ng-model="scopeVar"/></label><br /> <br /> <strong>Scope ID:</strong> {{$id}}<br /> <strong>Parent scope ID:</strong> {{$parent.$id}}<br /> <strong>Scope variable:</strong> {{scopeVar}}<br /> </div> </div> </body> </html>
angular.module("mainModule", []) .controller("mainController", function ($scope) { $scope.scopeVar = "Base scope value"; }) .directive("nghTranscludeTrue", function () { return { scope: {}, transclude: true, restrict: 'E', replace: true, template: '<div>' + '<h4>[Blue is the template content]</h4>' + '<label>Scope variable: <input type="text" ng-model="scopeVar"/></label><br />' + '<br />' + '<strong>Scope ID:</strong> {{$id}}<br />' + '<strong>Parent scope ID:</strong> {{$parent.$id}}<br />' + '<strong>Scope variable:</strong> {{scopeVar}}<br />' + '<div ng-transclude style="background-color: lightgreen;"></div>' + '</div>', link: function (scope, element, attrs) { scope.scopeVar = "Directive's scope value"; } }; }) .directive("nghTranscludeElement", function () { return { scope: {}, transclude: 'element', link: function (scope, element, attrs, controller, transcludeFn) { scope.scopeVar = "Directive's scope value"; var useDirectivesScope = false; var cloneTranscludedElement = false; var transcludedElement = null; var clonedElement = null; if (attrs.nghTranscludeElement == "3") { useDirectivesScope = true; } if (attrs.nghTranscludeElement == "2" || attrs.nghTranscludeElement == "3") { cloneTranscludedElement = true; } transcludedElement = transcludeFn(); if (cloneTranscludedElement) { if (useDirectivesScope) { transcludeFn(scope, function (clone) { clonedElement = clone; }); } else { transcludeFn(function (clone) { clonedElement = clone; }); } } if (clonedElement) { // Replace the background color and the title in the // clone to see it clearly. clonedElement.attr("style", "background-color: lightcoral;"); clonedElement.find("h4").text("[Red is the cloned content]"); // Append the clone after the original transcluded content element.after(clonedElement); } // We need to add the transcluded content because // we cannot use a template and it's up to us // to decide what to do with it. // AngularJS automatically adds a comment with // the directive's name (nghTranscludeElement) to // replace the transcluded element in its original // position inside the DOM, so we can just append // the transcluded content to the comment (element). element.after(transcludedElement); } }; });

Example


Description


We say that we transclude a content when we move it to a different DOM node, so transclusion is the name of this process. The transcluded content has an associated scope and this scope can be inherited from the original one or can be a different scope (see the scope example for more information on inherited scopes).

We typically use transclusion when we want to wrap arbitrary content in our custom directive.

The transclusion behavior depends on the value of the transclude property in the directive's definition object. When we add our directive to a DOM node, we can decide to transclude only the DOM subtree whose root is our node (transclude: true) or we can transclude the whole DOM node with all its subtree (transclude: 'element').

transclude: true

This case is shown in point 1 of the example. Here we've defined the nghTranscludeTrue directive that can be used as HTML element. The directive uses a template to replace the subtree of its own DOM node. The original DOM subtree is transcluded as a child of the div where the ng-transclude attribute appears. ng-transclude is a marker to tell AngularJS where to put the transcluded content inside the template. When the content is transcluded, AngularJS automatically creates a new inherited scope that is child of the one that was originally associated to the content. In the following image you can see a summary of what happens is point 1 of the example.

As you can check, after the execution, both the scopes are children of the base scope (check the parent scope's IDs), but the DOM subtree generated from the directive's template has the requested isolated scope (more information on isolated scopes in the scope example), while the transcluded content has an inherited scope automatically generated by AngularJS. This is visible also from the value of scopeVar and by changing its value through the text inputs you can notice the different behaviors of the isolated scope and the inherited scope.

transclude: 'element'

This case is shown in point 2 of the example. Here we've defined the nghTranscludeElement directive that can be used as HTML attribute. When we specify the transclude: 'element' option we cannot use a template, so we cannot even specify where to put the transcluded element with the ng-transclude attribute. We can anyway retrieve the element through the transclusion function available as a parameter of the link function and decide what to do with it. Take a look at the following image to understand point 2 of the example.

Through the fifth parameter of the link function we have access to transcludeFn. We can invoke this function in three different ways:

  • transcludeFn();
    In this case we just retrieve the transcluded content already bound to the new inherited scope created for us by AngularJS.

  • transcludeFn(function (clone)
    {
      ...
    });
        
    In this case we ask for a clone of the transcluded content and we get it through a synchronous callback function. The clone is already bound to a new inherited scope created for us by AngularJS. Each time we request a clone, a new inherited scope is generated, so each clone will be bound to a different scope.

  • transcludeFn(scope, function (clone)
    {
      ...
    });
        
    In this case we ask for a clone of the transcluded content and we get it through a synchronous callback function. We pass also a scope because we don't want AngularJS to automatically create an inherited scope for it, but we want instead to bind the clone to a different scope.

All the three cases are illustrated in points 2.1, 2.2 and 2.3 of the example respectively. We assign strings with the numbers 1, 2 and 3 to the directive's attribute in the HTML just to have a way to make the link function behave differently for each case that we want to test. In point 2.3 we assign the directive's scope to the cloned element so in fact that element is bound to the isolated scope.

General notes

  • in point 1 of the example we've defined an element directive (restrict: 'E') for transclude: true, but even another kind of directive could be used for the same transclusion option
  • in point 1 of the example we use the template property and the ng-transclude attribute to let AngularJS automatically transclude the content in the right position, anyway we could also access the content with the link function's fifth parameter transcludeFn if we need
  • in some examples in books or on the web you could find that the transclusion function is available also as a parameter of the directive's compile function, but this has been deprecated and the link function should be used instead (see the official documentation)
  • the same transclusion function that we have in the link function can be accessed also in a directive's controller by adding the $transclude parameter to its factory function