US & Canada: 877 849 1850
International: +1 678 648 3113

Accelebrate Blog

ACCELERATED LEARNING, CELEBRATED RESULTS

Understanding Transclusion in AngularJS (Part 2 of 2)

Knowing how a tool works, and knowing when to use that tool, are quite often two different things. The ability to apply a concept tends to be harder than learning the concept itself. In the first post on the topic of Angular.js transclusion, it was explained what transclusion is and how it is implemented in AngularJS directives. In this blog post, we will explore two scenarios where transclusion is a possible option for implementing a custom directive. I will also discuss best practices to help determine if transclusion should be used.

When Should Transclusion Be Used?

When should transclusion be considered an option? Typically, transclusion is employed in two situations. First, when a directive’s template hides the child content of the element on which the directive is applied and the child content is considered to be a template that should be displayed in conjunction with the directive’s template. Secondly, when the element (and its children) on which the directive is applied will serve as a template. In the first case, the transclude option is set to true, and in the second case the transclude option is set to ‘element’. When transclusion is used, it means AngularJS will pull the content from the DOM, and allow it to be accessed through the ngTransclude directive or the post-link transclude function. When transclusion is not used, but the content is needed to create a template (which is really what transclusion does for us), then the directive will need to use the structure of the content to produce an HTML fragment string, compile it, then link it to the DOM.

Decorated Plain HTML

One factor that determines whether or not transclusion should be used concerns the usage of HTML. One common structure is a template consisting of elements with directives applied to those elements. In this case, template does not mean a template loaded with the template or templateUrl option. Template refers to the original HTML structure on which the directives are applied before the compile process commences. When developing complex directives, there are several approaches that can be taken to apply the directives within the HTML structure. The simplest and most common approach is to decorate a standard HTML structure with attribute directive markers. Typically, decorating an HTML structure that will serve (more or less) as the final structure of the output means that transclusion of the content will probably be a good option. The decorating directives will modify the HTML structure to one degree or another (such as adding CSS classes or duplicating elements) but the resulting HTML structure will not be radically different from the original template. Consider the directive ngRepeat as shown in the HTML fragment below.

HTML Markup

<ul>
  <li ng-repeat="item in items">{{item}}</li>
</ul>

In the fragment, the HTML structure to be displayed is merely decorated by an attribute directive marker to repeat the LI element for each of the items in the items array. The template to be repeated by the directive is the LI element. This is an example of when transclusion (transclude ’element’ in the case of ngRepeat) makes sense. Another approach to structuring the HTML for a directive(s) is to use custom element(s) (all of which are directive markers). The custom element markers represent a domain specific component type language that purely describe structure. The structure will be translated into a final HTML DOM structure of standard HTML DOM elements. An example of this would be the following fragment of HTML.

HTML Markup

<unordered-list data="item in items">
{{item}}
</unordered-list>

This custom directive could result in HTML similar to the first example (depending upon the implementation of the unorderedList directive), but observe how the HTML structure of the directive element marker being used in the template looks nothing like final HTML to be rendered to the screen. In such a case, transclusion is not a good option.

Directive Based Components

Transclusion should only be used when the content being transcluded is intended to be directly added to the DOM. If the content is only structure that will merely be used as descriptive data to later construct the real HTML elements, then transclusion is not appropriate. Consider the following HTML fragment.

HTML Markup

<tab-set>
 <tab id="tab1" caption="Tab 1">
   Tab 1 Content
 </tab>
 <tab id="tab2" caption="Tab 2">
   Tab 2 Content
 </tab>
 <tab id="tab3" caption="Tab 3">
   Tab 3 Content
 </tab>
 <tab id="tab4" caption="Tab 4">
   Tab 4 Content
 </tab>
</tab-set>

There are no standard HTML elements named tabSet and tab . While these are elements in the DOM, they have no special meaning to the browser and ultimately will be processed to create standard HTML elements as coded by the tabSet and tab directives. In this case, transcluding this content would make little sense. Rather, the following scheme should be used. There should be no template on the tabSet directive so the compile/linking process will process the child tab elements (which are also directives) as normal. The tab directives should require the controller of the tabSet , then during their linking phase, they will use their HTML structure to register their descriptive data with the tabSet.

JavaScript Code for Tab Directive

{
 require: "^tabSet",
 link: function(scope, element, attrs, ctrl) {
   ctrl.registerTab({
     tabId: attrs.id,
     tabCaption: attrs.caption,
     tabContent: element.html()
   });
 }
}

After the child tab element directives have registered their descriptive data with the tabSet directive, then the linking process will execute the post-link function for the tabSet directive. When post-link function of the tabSet executes, the tabSet should use the descriptive data registered with its controller by the child content tabs to create the final HTML structure.

{
 link: function(scope, element, attrs, ctrl) {
    var tpl = "<ul><li ng-repeat='tab in tabs' ng-click='changeTab(tab.tabId)'>{{tab.tabCaption}}</li></ul>";
    tpl += "<div ng-repeat='tab in tabs' ng-if='currentTabId == tab.tabId'>{{tab.tabContent}}</div>";
    var linkingFn = $compile(tpl);
    var tabSetElements = linkingFn(scope);
    element.append(tabSetElements);
 }
}

This structure should then be compiled with compile service, linked to a scope, and added to the DOM replacing the original custom element structure of the template. Using the HTML structure of the tabSet and tab elements along with the directive JavaScript code above, the following HTML is produced by the two directives.

HTML Markup

<ul class="ng-scope">
 <li ng-repeat="tab in tabs" ng-click="changeTab(tab.tabId)" class="ng-binding ng-scope">Home</li>
 <li ng-repeat="tab in tabs" ng-click="changeTab(tab.tabId)" class="ng-binding ng-scope">Contact</li>
 <li ng-repeat="tab in tabs" ng-click="changeTab(tab.tabId)" class="ng-binding ng-scope">About</li>
 <li ng-repeat="tab in tabs" ng-click="changeTab(tab.tabId)" class="ng-binding ng-scope">Mission</li>
</ul>
<div ng-repeat="tab in tabs" ng-if="currentTabId == tab.tabId" class="ng-binding ng-scope">
 Tab 1 Content
</div>
<div ng-repeat="tab in tabs" ng-if="currentTabId == tab.tabId" class="ng-binding ng-scope">
 Tab 2 Content
</div>
<div ng-repeat="tab in tabs" ng-if="currentTabId == tab.tabId" class="ng-binding ng-scope">
 Tab 3 Content
</div>
<div ng-repeat="tab in tabs" ng-if="currentTabId == tab.tabId" class="ng-binding ng-scope">
 Tab 4 Content
</div>

The original HTML template containing the tabSet and tab elements adequately describes the HTML shown above but it does not contain any of the actual HTML elements that will be used to build the final HTML structure of the tab set. Therefore, transcluding the original template is of little use; therefore, using the structure to construct a string based template which is then compile and linked manually is more appropriate.

Best Practices for Transclusion

Transclusion is best used when the content being transcluded represents the actual HTML content that needs to be displayed. The HTML content may need to be manipulated in some fashion, but the content itself is in fact HTML content that needs to be added to the DOM. Tranclusion is not the best option when the content represents a domain-specific component type language composed of elements, especially custom elements, for the purpose of purely defining a structure that will be transformed into standard HTML. In such situations, transclusion should not be used. Instead, a string template of HTML should be constructed, passed to the compile service and manually linked into the DOM. Manipulating a string of HTML with the data collected from the structured elements is much easier and more maintainable than trying to perform a series of DOM manipulations on transcluded DOM elements that bear no resemblance to the final outputted HTML content.

Conclusion

Transclusion is a powerful idea and tool that is used to create custom AngularJS directives. Effective transclusion of content requires the developer to understand how the HTML template on which the directives are applied will be used. Directives that only decorate an HTML template whose structure is very similar to the final HTML structure should use transclusion. Directives (usually applied as element markers) that represent more of a component-based structure that describes the final HTML structure, without providing elements, should not use transclusion. AngularJS directives are a very capable framework for building simple to very complex HTML structures and even components. With the rise of component-based web programming, understanding and effectively applying directives, especially transclusion, is key to the success of modern web application projects.


Author: Eric Greene, one of Accelebrate’s instructors.

Accelebrate offers private AngularJS training and JavaScript training for groups and instructor-led online JavaScript classes for individuals.

Categories: JavaScript Articles
Tags: ,

15 Responses to "Understanding Transclusion in AngularJS (Part 2 of 2)"

Leave a Reply

Your email address will not be published. Required fields are marked *

Your email address will not be published. Required fields are marked *

*



You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Please contact us for GSA pricing.
Contract #GS-35F-0307T

Please see our complete list of
Microsoft Official Courses