Understanding Transclusion in AngularJS (Part 2 of 2)

February 24, 2015 in JavaScript Articles

Written by Eric Greene


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.


Written by Eric Greene

Eric Greene

Eric is a professional software developer specializing in HTML, CSS, and JavaScript technologies. He has been developing software and delivering training classes for nearly 19 years. He holds the MCSD Certification for ASP.Net Web Applications, and is a Microsoft Certified Trainer.



Contact Us:

Accelebrate’s training classes are available for private groups of 3 or more people at your site or online anywhere worldwide.

Don't settle for a "one size fits all" public class! Have Accelebrate deliver exactly the training you want, privately at your site or online, for less than the cost of a public class.

For pricing and to learn more, please contact us.

Contact Us Train For Us

Toll-free in US/Canada:
877 849 1850
International:
+1 678 648 3113

Toll-free in US/Canada:
866 566 1228
International:
+1 404 420 2491

925B Peachtree Street, NE
PMB 378
Atlanta, GA 30309-3918
USA

Subscribe to our Newsletter:

Never miss the latest news and information from Accelebrate:

Microsoft Gold Partner

Please see our complete list of
Microsoft Official Courses

Recent Training Locations

Alabama

Huntsville

Montgomery

Birmingham

Alaska

Anchorage

Arizona

Phoenix

Tucson

Arkansas

Fayetteville

Little Rock

California

San Francisco

Oakland

San Jose

Orange County

Los Angeles

Sacramento

San Diego

Colorado

Denver

Boulder

Colorado Springs

Connecticut

Hartford

DC

Washington

Florida

Fort Lauderdale

Miami

Jacksonville

Orlando

Saint Petersburg

Tampa

Georgia

Atlanta

Augusta

Savannah

Idaho

Boise

Illinois

Chicago

Indiana

Indianapolis

Iowa

Ceder Rapids

Des Moines

Kansas

Wichita

Kentucky

Lexington

Louisville

Louisiana

Baton Rouge

New Orleans

Maine

Portland

Maryland

Annapolis

Baltimore

Hagerstown

Frederick

Massachusetts

Springfield

Boston

Cambridge

Michigan

Ann Arbor

Detroit

Grand Rapids

Minnesota

Saint Paul

Minneapolis

Mississippi

Jackson

Missouri

Kansas City

St. Louis

Nebraska

Lincoln

Omaha

Nevada

Reno

Las Vegas

New Jersey

Princeton

New Mexico

Albuquerque

New York

Buffalo

Albany

White Plains

New York City

North Carolina

Charlotte

Durham

Raleigh

Ohio

Canton

Akron

Cincinnati

Cleveland

Columbus

Dayton

Oklahoma

Tulsa

Oklahoma City

Oregon

Portland

Pennsylvania

Pittsburgh

Philadelphia

Rhode Island

Providence

South Carolina

Columbia

Charleston

Spartanburg

Greenville

Tennessee

Memphis

Nashville

Knoxville

Texas

Dallas

El Paso

Houston

San Antonio

Austin

Utah

Salt Lake City

Virginia

Richmond

Alexandria

Arlington

Washington

Tacoma

Seattle

West Virginia

Charleston

Wisconsin

Madison

Milwaukee

Alberta

Edmonton

Calgary

British Columbia

Vancouver

Nova Scotia

Halifax

Ontario

Ottawa

Toronto

Quebec

Montreal

Puerto Rico

San Juan

© 2013-2019 Accelebrate, Inc. All Rights Reserved. All trademarks are owned by their respective owners.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.