Transclusion In Custom AngularJS Directive

In this article you will learn about Transclusion in custom AngularJS Directive.

Transclusion simply means inclusion of one document or a part of it into another document. It will allow us to include some content (could be textual or graphical) or part of html inside our custom AngularJs directive.

If we place some content inside our custom AngularJS directive by default it won’t work (i.e. the content or html won’t be displayed when the custom directive is rendered on the browser) because transclude property of custom AngularJS directive is by default set to “false”. To make it work we need to set the Transclude property to “True” inside our custom directive. Transclusion can also be useful when we want to nest the custom directives (i.e. using custom directive inside another custom directive). Transclusion property in AngularJS custom directive can hold value either of “true”, “false” or “element”.

Let’s define our application architecture. For demonstrating this I am using ASP.NET MVC4 application. Inside my application I’ve added a new folder with the name “Angular” which will hold all my AngularJS components like directives, page scripts & html pages. I’ve added bootstrap and AngularJS reference through Nuget. The following is the snapshot of my application architecture.

script

Let’s create the base of the application. I’ve created a base JavaScript file which will hold the reference of my AngularJS module. The following is the code snippet of the file.

  1. //angularJs Module  
  2. var myApp = angular.module("myApp", ["ngRoute"]);  
I’ve added a new html file named “Index.html” which will be the home page of our application. The following is the code snippet for the same.

Index.html
  1. <!DOCTYPE html>  
  2. <html xmlns="http://www.w3.org/1999/xhtml" ng-app="myApp">  
  3. <head>  
  4.     <title>Transclusion Demo</title>  
  5.     <!--bootstrap reference-->  
  6.     <link href="Content/bootstrap.min.css" rel="stylesheet" />  
  7.     <!--jQuery reference-->-->  
  8.     <script src="Scripts/jquery-1.9.1.min.js"></script>  
  9.     <!--AngularJs reference-->  
  10.     <script src="Scripts/angular.min.js"></script>  
  11.     <script src="Scripts/angular-route.min.js"></script>  
  12.     <!--PageScripts Reference-->  
  13.     <script src="Angular/PageScripts/angularApp.js"></script>  
  14.     <script src="Angular/PageScripts/connectAppService.js"></script>  
  15.     <script src="Angular/PageScripts/navController.js"></script>  
  16.     <script src="Angular/PageScripts/transclusionAsFalse.js"></script>  
  17.    <!--Just keep on adding the new reference file for every new page-->  
  18. </head>  
  19. <body class="container">  
  20.     <header>  
  21.         <h1>Transclusion in AngularJs</h1>  
  22. <!--Creating Menus-->         
  23.  <div ng-controller="navController" style="margin-top:50px;">  
  24.             <ul class="nav nav-pills">  
  25.                 <li role="presentation" ng-class="{'active': isActive('/TransclusionAsFalse')}">  
  26.                     <a href="#/TransclusionAsFalse">Transclude As False</a>  
  27.                 </li>  
  28.                 <li role="presentation" ng-class="{'active': isActive('/TransclusionAsTrue')}">  
  29.                     <a href="#/TransclusionAsTrue">Transclude As True</a>  
  30.                 </li>  
  31.                 <li role="presentation" ng-class="{'active': isActive('/TransclusionAsElement')}">  
  32.                     <a href="#/TransclusionAsElement">Transclude As Element</a>  
  33.                 </li>  
  34.             </ul>  
  35.         </div>  
  36.     </header>  
  37.     <section>  
  38.         <div ng-view>  
  39.         </div>  
  40.     </section>  
  41. </body>  
  42. </html>  
For routing I’ve added a new JavaScript file with the name “navController.js”. The following is the code snippet for the same.
  1. //Application Routing Configuration  
  2. myApp.config(function ($routeProvider) {  
  3.   
  4.     $routeProvider  
  5.     .when("/", {  
  6.         templateUrl: "Angular/Pages/TransclusionAsFalse.html",  
  7.         controller: "transcludeFalseController"  
  8.     })  
  9.    .when("/TransclusionAsFalse", {  
  10.        templateUrl: "Angular/Pages/TransclusionAsFalse.html",  
  11.        controller: "transcludeFalseController"  
  12.    })  
  13.     .when("/TransclusionAsTrue", {  
  14.         templateUrl: "Angular/Pages/TransclusionAsTrue.html",  
  15.         controller: "transcludeTrueController"  
  16.     })  
  17.     .when("/TransclusionAsElement", {  
  18.         templateUrl: "Angular/Pages/TransclusionAsElement.html",  
  19.         controller: "transcludeElementController"  
  20.     })  
  21.     .otherwise({  
  22.         redirectTo: "../../Index.html"  
  23.     })  
  24. });  
  25.   
  26. myApp.controller("navController", ["$scope""$location"function ($scope, $location) {  
  27.    //This function is used for highlighting the clicked menu.  
  28.     $scope.isActive = function (locationUrl) {  
  29.         return locationUrl === $location.path();  
  30.     };  
  31. }]);  
Now let’s have a look at each of the transclude property values by demonstrating each of them.
  1. “False”: This is the default value for the transclude property. With transclude value as “false” if we add some content or part of html inside our custom AngularJS directive declaration, then in that case AngularJS will only render the directive and the added content or html will be ignored.

    To demonstrate this I’ve added a new JavaScript file with the name “TransclusionAsFalse.js”. The following are its contents:
    1. myApp.controller("transcludeFalseController", ["$scope"function ($scope) {  
    2.   
    3. }]);  
    4. //Tranclusion is by default false  
    5. myApp.directive("myDirective"function () {  
    6.     return {  
    7.         restrict: "E",  
    8.         template: "<div class='panel panel-default'><div class='panel-heading'>Vishal Gilbile</div><div class='panel-body'>Address: Andheri (E)</div></div>"  
    9.     }  
    10. });  
    Here is the page where I’ve used this directive.

    TransclusionAsFalse.html
    1. <h2>Transclusion As False</h2>  
    2.   
    3. <div ng-controller="transcludeFalseController" class="table-responsive">  
    4.     <!--using the directive-->  
    5.     <my-directive>  
    6.     </my-directive>  
    7. </div>  
    Now let’s run the application and you’ll see the following output.

    TransclusionAsFalse

    Now let’s add some content inside our custom directive declaration. Here is the updated TransclusionAsFalse.html file.
    1. <h2>Transclusion As False</h2>  
    2.   
    3. <div ng-controller="transcludeFalseController" class="table-responsive">  
    4.     <!--using the directive-->  
    5.     <my-directive>  
    6.         <strong>Hello playing with transclude property of custom angularJs directive.</strong>  
    7.     </my-directive>  
    8. </div>  
    Now let’s run the application and you’ll see the following output.

    custom directive declaration

    As mentioned earlier whenever the transclude property value is false and if we add some content inside our custom directive declaration then at the time of rendering the custom directive AngularJS simply ignore the added content and only render the custom directive.

  2. “True”: With transclude property value setting to true will notify the AngularJS to render the contents defined inside the custom directive declaration. Inside the custom directive we need to tell AngularJS where the added contents should be displayed, to do this we need to use ng-transclude attribute inside our custom AngularJS directive which will act as a placeholder for the added contents.

    To demonstate this I’ve added a new JavaScript file with the name “transclusionAsTrue.js”. The following are the contents for the same.
    1. //angularController  
    2. myApp.controller("transcludeTrueController", ["$scope""ConnectAppDataService"function ($scope, ConnectAppDataService) {  
    3.     $scope.users = ConnectAppDataService.GetConnectAppUserDetails();  
    4. }]);  
    5.   
    6.   
    7. //PostContent Directive  
    8. myApp.directive("postContent"function () {  
    9.     return {  
    10.         restrict: "E",  
    11.         templateUrl: "Angular/Directives/postContent.html",  
    12.         scope: {  
    13.             userDetailsArray: "=",  
    14.         }  
    15.     }  
    16. })  
    17.   
    18. //UserPost Directive  
    19. myApp.directive("userPost"function () {  
    20.     return {  
    21.         restrict: "E",  
    22.         templateUrl: "Angular/Directives/userPosts.html",  
    23.         transclude: true,  
    24.         scope: {  
    25.             userName: '@',  
    26.             postDetails: "="  
    27.         }  
    28.     }  
    29. });  
    30.   
    31. //UserLikes Directive  
    32. myApp.directive("userLikes"function () {  
    33.     return {  
    34.         restrict: "E",  
    35.         templateUrl: "Angular/Directives/userLikes.html",  
    36.         scope: {  
    37.             postLikesCount: "@"  
    38.         }  
    39.     }  
    40. });  
    I’ve defined the directives and the following are the code snippet for each of them.

    postContent.html
    1. <div ng-repeat="user in userDetailsArray">  
    2.     <user-post user-name="{{user.Name}}" post-details=" post" ng-repeat="post in user.Posts">  
    3.         <user-likes post-likes-count="{{post.Likes.length}}"></user-likes>  
    4.     </user-post>  
    5. </div>  
    userPosts.html
    1. <div class="panel panel-default panel-primary">  
    2.     <div class="panel-heading">  
    3.         <h5><strong>{{userName}}</strong></h5>  
    4.     </div>  
    5.     <div class="panel-body">  
    6.         <img ng-src="{{postDetails.Content}}" alt="Image" class="img-responsive" />  
    7.     </div>  
    8.     <div class="panel-footer" ng-transclude>  
    9.     </div>  
    10. </div>  
    userLikes.html
    1. <div>  
    2.     <button class="btn btn-primary" type="button">  
    3.         Likes <span class="glyphicon glyphicon-thumbs-up"></span>  
    4.     </button>  
    5.     <!--Binding the likesCount model-->  
    6.     <span class="badge">{{postLikesCount}}</span>  
    7. </div>  
    Here is the code snippet for the page where we have included this directive.

    TransclusionAsTrue.html
    1. <h2>Transclusion As True</h2>  
    2. <div ng-controller="transcludeTrueController" class="table-responsive">  
    3.     <post-content user-details-array="users">  
    4.     </post-content>  
    5. </div>  
    Let’s run the application and here is the output.

    TransclusionAsTrue

  3. “Element”: When the transclude property value is set to “element” it will transclude the full directive i.e. it will render the directive as comment. This value should be used only when we want to transclude the whole element on which the directive is applied including any directive defined with low priority. Whenever we set the value of the transclude property to “element” it provides us with the link function on which can have access to the scope, element, attributes, controller & transclude function. This transclude function enables you to clone the element and also to bound data to the scope within it.

    To demonstrate this I’ve created a new javascript file with the name “transclusionAsElement.js”. The following is the code snippet for the same.
    1. //angularJs Controller  
    2. myApp.controller("transcludeElementController", ["$scope"function ($scope) {  
    3.   
    4.     $scope.userList = ["Vishal Gilbile""Rahul Bandekar""Lincy Pullan""Muthu Konar"];  
    5.   
    6.     $scope.addUser = function () {  
    7.         var userName = $("#txtUserName").val();  
    8.         if ($scope.userList.indexOf(userName) == -1) {  
    9.             $scope.userList.push(userName);  
    10.         }  
    11.         $("#txtUserName").val("");  
    12.     }  
    13.   
    14.     $scope.removeUser = function () {  
    15.         var userName = $("#txtUserName").val();  
    16.         var index = $scope.userList.indexOf(userName);  
    17.         if (index > -1) {  
    18.             $scope.userList.splice(index, 1);  
    19.         }  
    20.         $("#txtUserName").val("");  
    21.     }  
    22. }]);  
    23.   
    24. //UserListOperation Directive  
    25. myApp.directive("userListOperation"function () {  
    26.     return {  
    27.         restrict: "E",  
    28.         transclude: "element"  
    29.     }  
    30. });  
    Here is the html code snippet on which the directive is used.

    TransclusionAsElement.html
    1. <h2>Transclusion As Element</h2>  
    2. <div ng-controller="transcludeElementController">  
    3.      <user-list-operation>  
    4.         <ul class="list-group">  
    5. <li class="list-group-item" ng-repeat="user in userList"> {{user}}</li>  
    6.         </ul>  
    7.  </user-list-operation>      
    8.  <div class="row">  
    9.         <div class="col-xs-2">  
    10.             Enter User Name:  
    11.         </div>  
    12.         <div class="col-xs-4">  
    13.             <input id="txtUserName" type="text" required class="form-control" />  
    14.         </div>  
    15.     </div>  
    16.     <div class="row" style="margin-top:10px; text-align:center;">  
    17.         <div class="col-xs-4">  
    18.             <button class="btn btn-default btn-primary" ng-click="addUser()">Add User</button>  
    19.             <button class="btn btn-default btn-primary" ng-click="removeUser()">Remove User</button>  
    20.         </div>  
    21.     </div>  
    22. </div>  
    Now let’s run the application and you’ll see the following output.

    output

    As you can see that after setting the value of the transclude property to element it just replaces the directive content with the html comment “<!-- userListOperation: undefined -->”. Now let’s try to introduce the link function inside our directive. Here is the updated code snippet for the “transclusionAsElement.js” userListOperation directive.
    1. //UserListOperation Directive  
    2. myApp.directive("userListOperation"function () {  
    3.     return {  
    4.         restrict: "E",  
    5.         transclude: "element",  
    6.         link: function (scope, el, attrs, cntrl, transclude) {  
    7.             console.log(scope);  
    8.             transclude(scope, function (clone) {  
    9.                 el.before(clone);  
    10.             })  
    11.         }  
    12.     }  
    13. });  
    Now let’s run the application and here is the output.

    run the application

You can add or remove a user by entering the details and by clicking the “Add User” or “Remove User” button respectively and accordingly the newly added elements would be displayed in the list and existing user entry would be removed from the list.