Using Directives as Data Templates in AngularJS

  • Estimated read time: 6 min read
  • Written by Chad Campbell on Nov 2nd 2013

AngularJS empowers developers to create custom HTML components. These components are usually HTML elements, attributes, or classes. These custom components can be created with the help of a directive. With this in mind, we can use a directive to create a reusable data template.

A data template is used to setup how a data object looks visually. Data templates are useful when working with a list of items that share a similar visual structure, but have different data. A baseball card is a real world example of a data template.

In this blog post, we'll create a data template named "my-ballcard". To see what the end result of this post will look like, click here. To jumpstart this post, we'll expand on the example used to display groups in AngularJS. Let's start by defining the directive for our ballcard.

Step 1: Define the Directive

When defining a directive, you must choose a name. In this example, we'll use "myBallcard." You may have noticed the absense of the hyphen (-). The reason why is because AngularJS normalizes directive names. We'll go further into this idea after the following code snippet.

angular.module("demo", []).directive("myBallcard", function () {
  return {
    restrict: "E",
  }
});

The previous directive uses a traditional case-sensitive camelCase name. When referenced declaratively in the HTML DOM, the directive can be referenced by its case-insensitive, dash-delimited name, my-ballcard. This approach is done to create consistency in each respective world: the imperative JavaScript world and the declarative HTML world.

To bridge the two worlds, AngularJS takes a custom HTML element's name or attribute and removes any "x-" or "data-" from the beginning. Then, AngularJS converts ':', '-', or '_' delimeters to camelCase. This is how AngularJS looks for the directive's definition. For this reason, the following element names would reference the same directive:

<my-ballcard ... />
<my:ballcard ... />
<my_ballcard ... />
<data-my-ballcard ... />
<x-my-ballcard ... />

In our example, we gave the ballcard directive a prefix of "my". A prefix helps minimize the risk of name collisions. In addition, it makes code more readable by providing more context. While the latter is more of an opinion, AngularJS allows us to specifically set a directive's declaration style.

AngularJS allows us to set how we want to use a directive. The are four options: HTML element, attribute, class and/or comment. This can be configured through the restrict property. In the code snippet above, we specifically used 'E', to mark the directive for use as an HTML element. We did this because by default, a directive's restrict property is set to 'A' for attribute. The other two options are 'C' for class and 'M' for comment. If you want, you can even use multiple options for each directive. Regardless of which options you choose, you still need to consider the template.

Step 2: Define the Template

To move closer to our goal of using directives as data templates, we need to look at using the template or templateUrl properties. These properties determine the HTML that will be placed where our directive is used. The template option is a way to set the HTML inline. For more complex HTML, you may use the templateUrl property to tell AngularJS to look in a seperate .html file for the template. In our example, we'll use the simpler template option as highlighted here:

angular.module("demo", []).directive("myBallcard", function () {
  return {
    restrict: "E",
    template: "
" + "
{{playerData.firstName}} {{playerData.lastName}}
" + "
{{playerData.position}} - {{playerData.number}}
" } });

The code above basically says a player's name will appear in bold letters. The player's position and number will appear on the next line. If you inspect the HTML DOM, you may notice that the my-ballcard elements are written out. You can remove these, and inject only the HTML from your template, by setting the replace property to true in the directive definition above.

You may have noticed the use of mustaches (curly braces) in the template. These curly braces are a way to declaratively tell AngularJS to replace this text with some data. That data, and the playerData variable, are setup in the scope.

Step 3: Setup the Scope

The scope property lets us set the data context. By default, a directive will inherit the data context of its closest parent. Based on our previous code snippet, the nearest parent is the TeamListCtrl. That data context doesn't have the information we want (player firstName, lastName, position, and number). To change the data context from the team level to the player level, we first define the scope as shown here.

angular.module("demo", []).directive("myBallcard", function () {
  return {
    restrict: "E",
    scope: { playerData: "=player" },
    template: "
" + "
{{playerData.firstName}} {{playerData.lastName}}
" + "
{{playerData.position}} - {{playerData.number}}
" } });

In this code snippet, we are building the bridge between our directive and the HTML world. The playerData property is the name of the variable we'll reference in the directive template. The player word sets the name of the attribute we can set in the HTML world. To glue the two worlds, we'll use a little syntactical magic.

The glue is set by a single character. That character can be a '@', '=', or '&'. The at sign (@) sets one-way binding from the source to the target. This is acheived by passing the variable by value. To use two-way binding, the equals sign (=) can be used, which passes the variable by reference. This approach was used in the snippet above. Finally, there's the ampersand (&) which means we should expect an expression. Either way, your character choice must preceed the name of the HTML attribute. Once done, you can set the scope in the HTML world as shown here:

<div ng-repeat="p in team.players" class="item">
    <my-ballcard player="p"></my-ballcard>
</div>

These three steps show how to use a directive as a data template in AngularJS. If you want to see a completed example, you can view it here. The code used for this blog post can pulled from GitHub or downloaded here. If you have any questions or comments related to this post, please leave them in the comments below. If you would like other help from Ecofic, please contact us.


Comments

comments powered by Disqus

Chad Campbell
Chad Campbell

Chad is an independent software professional. He has been named a Microsoft MVP five times. His books have been translated into multiple languages and distributed worldwide. He holds a computer science degree from Purdue University, where he also studied psychology.

Chad has built sites, apps, frameworks, libraries, and platforms using Java, .NET, and Node. He's ran his own startups and has created software for Fortune 100 companies. In short, Chad knows how to create software. From ideation to delivery. From start-to-finish.


Follow Chad Online