Directives make up an important part in AngularJS applications with AngularJS. They manipulate the Document Object Model (DOM), route events to event handler functions, and much more. Through the use of custom directives, we can build applications with a rich user interface.
Although the built-in directives such as ng-repeat
, ng-show
, and ng-hide
cover many different scenarios, you will often need to create application-specific directives. This chapter will give you an overview on how to create and customize simple AngularJS directives, with the best practices in mind.
We assume that you already know what directives are, so let's check how to create and customize some simple directives to manipulate the DOM.
The basic form to create an AngularJS directive is very simple and intuitive. Let's take a look at a basic way to declare a directive using inline HTML:
.directive("directiveName",function () { return { restrict: 'A', controller: function() { // Directive Controller }, link: function() { // Link function }, template: '' } });
As the name implies, we include the HTML template within the code of the directive through the template
property.
Let's see a practical example to show some text on the screen.
The following example is very simple and easy to understand. Imagine that we have set up an AngularJS application called app
and want to display some simple text in the browser with the following content: Hello Simple Directive
.
For this recipe, we will use a simple HTML file with AngularJS script in the head
tag.
Add myFirstDirective
as a dependence to the app
application:
angular.module('app', ['myFirstDirectives']);
So, we can declare and inject the module that contains our directive into our application. Following the best practices to include new dependencies on the AngularJS application, we called the directive as helloSimpleDirective
:
angular.module('myFirstDirective') .directive('helloSimpleDirective', function() { return { scope: true, // inherits child scope from parent. restrict: 'E', // An Element Directive. replace: true, template: '<h3>Hello Simple Directive</h3>' }; });
Now, before we look into the code, we need to remember that we have the following four types of directives and that we can use more than one each time:
An HTML element (
<directive-type></directive-type>
), represented by the letterE
An attribute on an element (
<input type="text" directive-type/>
), represented by the letterA
As a class (
<input type="text" class="directive-type"/>
), represented by the letterC
As a comment (
<!--directive:directive-type-->
), represented by the letterM
We will see more about this in the later chapters.
In the first line of code, we named the application module as myFirstDirective
and added the directive called helloSimpleDirective
as a module. It's very simple to use this directive too. Just declare it like any other HTML tag (in this case, an element), as shown in the following code:
<hello-simple-directive></hello-simple-directive>
In the previous code, our angular.module('app', [myFirstDirective])
function serves to register the new directive to the AngularJS application. On the directive, the first string argument is the directive name 'hellosimpledirective'
and the second argument is a function that returns a Directive Definition Object (DDO). Also, if the directive has some external object/service dependencies such as $http
, $resource
, and $compile
, among others, they can be injected here.
Note that we have declared the directive as an HTML element, and the sign -
has delimited strings to camelCase, so the name helloSimpleDirective
will be converted to hello-simple-directive
to be used as the directive name.
In this basic example, we just print on the screen the h3
HTML tag
with the text Hello Simple Directive
.
You can read more about directives on the official AngularJS documentation at https://docs.angularjs.org/guide/directive
Modal windows are interface components often used in web applications. Building them is very simple and is done using libraries such as Dojo or jQuery, but implementing them in AngularJS applications is not as simple, since the DOM manipulation is restricted to directives.
Next, we will see how to use this component in a very simple way.
Let's start placing the following HTML code in a new blank page. The following code has all the basic requisites to illustrate the example:
<!DOCTYPE html> <html> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.x/angular.js"></script> <title>Modal Window Directive</title> <style> .modal-overlay { position:absolute; z-index:9999; top:0; left:0; width:100%; height:100%; background-color:#000; opacity: 0.8; } .modal-background { z-index:10000; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #fff; } .modal-content { padding:10px; text-align: center; } .modal-close { position: absolute; top: 5px; right: 5px; padding: 5px; cursor: pointer; display: inline-block; } </style> </head> <body ng-app='SimpleModal'> </body> </html>
For this simple example, we placed the CSS code inside the style
tag on the same HTML file; don't do that in production.
Now we can create our modal directive with the following code:
// Creating a simple Modal Directive app = angular.module('SimpleModal', []); app.directive('modalWindow', function() { return { restrict: 'E', scope: { show: '=' }, replace: true, // Replace with template transclude: true, // To use custom content link: function(scope, element, attrs) { scope.windowStyle = {}; if (attrs.width) { scope.windowStyle.width = attrs.width; } if (attrs.height) { scope.windowStyle.height = attrs.height; } scope.hideModal = function() { scope.show = false; }; }, template: "<div ng-show='show'><div class='modal-overlay' ng-click='hideModal()'></div><div class='modal-background' ng-style='windowStyle'><div class='modal-close' ng-click='hideModal()'>X</div><div class='modal-content' ng-transclude></div></div></div>" }; });
Add the controller's code:
app.controller('ModalCtrl', ['$scope', function($scope) { $scope.modalShown = false; $scope.toggleModal = function() { $scope.modalShown = !$scope.modalShown; }; } ]);
Finally, include the directives tags into the
body
tag of our HTML file:<div ng-controller='ModalCtrl'> <button ng-click='toggleModal()'>Open Modal</button> <modal-window show='modalShown' width='400px' height='60%'> <p>Hello Simple Modal Window<p> </modal-window> </div>
The work here is very simple; we just placed an HTML template using the inline template, as we did in the previous example:
template: "<div ng-show='show'><div class='modal-overlay' ng-click='hideModal()'></div><div class='modal-background' ng-style='windowStyle'><div class='modal-close' ng-click='hideModal()'>X</div><div class='modal-content' ng-transclude></div></div></div>"
As we build everything from scratch, we need to style the HTML tags with CSS classes for a better look using the style
tag inside the head
element. In production applications, you must have a separated file for CSS styles.
The inline template contains the built-in directives ng-show()
and ng-style()
, along with a ng-click()
function to hide the modal.
The ng-style()
directive is not used often, but we include it in this example just to illustrate how we can place inline styles inside a directive.
Inline templates are very useful, but not too flexible. On large application managers, different inline templates can be very painful to use and take a lot of time. Use them with small templates. In the next recipe, we will see how to use external templates on custom directives.
We can also use the ng-transclude
in-built directive to remove any content from the DOM before the modal content inserted.
You can read more about the use of
ng-transclude
from the AngularJS official documentation at https://docs.angularjs.org/api/ng/directive/ngTransclude
Thinking in terms of best practices, let's see how to use the same modal directive with an external template, using the templateUrl
property instead of the template
property. Before we go further, let's explore the two ways to use templates.
Use the script
tag of ng-template
, as shown in the following example:
<body ng-app='SimpleModal'> <script type="text/ng-template" id="modal.html"> <div ng-show='show'> <div class='modal-overlay' ng-click='hideModal()'></div> <div class='modal-background' ng-style='windowStyle'> <div class='modal-close' ng-click='hideModal()'>X</div> <div class='modal-content' ng-transclude></div> </div> </div> </script> </body>
Alternatively, place the HTML content in a separate file; in this case, the template will be an external file, not just external from the directive code. The code is as follows:
<body ng-app='SimpleModal'> <div ng-controller='ModalCtrl'> <button ng-click='toggleModal()'>Open Modal</button> <modal-window show='modalShown' width='400px' height='60%'> <p>Hello Simple Modal Window with External Template<p> </modal-window> </div> </body>
Both ways have the same result, and we will see the difference later. For now, let's focus on the HTML template.
Let's replace the entire template string with the following code:
// loading external templates app = angular.module('SimpleModal', []); app.directive('modalWindow', function() { return { restrict: 'E', scope: { show: '=' }, replace: true, // Replace with template transclude: true, // To use custom content link: function(scope, element, attrs) { scope.windowStyle = {}; if (attrs.width) { scope.windowStyle.width = attrs.width; } if (attrs.height) { scope.windowStyle.height = attrs.height; } scope.hideModal = function() { scope.show = false; }; }, templateUrl: "modal.html" }; });
Remember that we keep the same controller code as the previous example. The
templateUrl
property points to an external file, so place the following code in a blank HTML file and save it asmodal.html
:<body ng-app='SimpleModal'> <div ng-controller='ModalCtrl'> <button ng-click='toggleModal()'>Open Modal</button> <modal-window show='modalShown' width='400px' height='60%'> <p>Hello Simple Modal Window with External Template<p> </modal-window> </div> </body>
With the templateUrl
property, we can load an external HTML template inside our current HTML file. It is very useful to use this practice because we can reuse the same template in different places in the application. We will cover this topic later on in this book.
When we use type=text/ng-template
with the script
tag, we need to place the modal content inside our page, and the content will be hidden by the browser. The script
tag is used to tell the browser that there is a code snippet, usually in JavaScript. In this way, the content of the tag is interpreted differently by the browser. In our case, the type
attribute indicates that we have a template, as we can see in the previous example.
We can use the same example, as shown in the following code:
<!DOCTYPE html> <html> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.x/angular.js"></script> <title>Modal Window Directive</title> <style> ... </style> </head> <body ng-app='SimpleModal'> <script type="text/ng-template" id="modal.html"> <div ng-controller='ModalCtrl'> <button ng-click='toggleModal()'>Open Modal</button> <modal-window show='modalShown' width='400px' height='60%'> <p>Hello Simple Modal Window using ng-template<p> </modal-window> </div> </script> </body> </html>
We also recommend reading the
ng-include
documentation. As we are talking about HTML templates, you can find more information at https://docs.angularjs.org/api/ng/directive/ngInclude.
Now let's take a look at the link
function property inside the directive. The template generated by a directive is meaningless unless it is compiled with the appropriate scope. Thus, by default, a directive does not get a new child scope. Instead, it is related to the parent scope. This means that if the directive is present within a controller, then it will use this controller scope instead of creating a new one.
To use this scope, we need to use the link
function. We achieve this by using the link
property inside the directive definition. Let's use a basic example to understand this.
Let's place the following code inside a new blank HTML file:
<!DOCTYPE html> <html ng-app="linkdirectives"> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.4/angular.js"></script> <title>Link Function Directive</title> </head> <body ng-controller="LinkCtrl"> <input type="text" ng-model="colorName" placeholder="Insert a color name"/> <link-function></link-function> </body> </html>
Now let's add the directive code.
Here's the directive code, using simple CSS manipulation:
app.directive('linkFunction',function(){ return{ restrict: 'AE', replace: true, template: '<p style="background-color:{{colorName}}">Link Function Directive</p>', link: function(scope,element,attribute){ element.bind('click',function(){ element.css('background-color','white'); scope.$apply(function(){ scope.color="white"; }); }); element.bind('mouseover',function(){ element.css('cursor','pointer'); }); } } });
The link
function takes three arguments: scope
, element
, and attribute
. For a better understanding, we use the entire name for the arguments, without any abbreviation. It is also very common to see elem
for element
and attrs
for attribute
.
The element
argument is a short version from jQuery Lite that is already included in AngularJS to manipulate the DOM without the need to use the famous $()
from jQuery.
The scope
element is the same from the parent controller, and the link
function is used for attaching event listeners to DOM elements. Always watch the model properties for changes, and update the DOM with the new information. In this case, we used the $apply()
function to update the binding.
The link
function contains code used after the compilation process, such as some DOM manipulation or jQuery use. Also, the controller $scope
and scope
of the link
function are almost the same thing.
When you use the scope
element as the first parameter of the link
function inside a directive, it has the same behavior of the $scope
element from a controller. However, when you declare the scope: {}
property with an empty object inside the directive, you create an isolate scope and both are different. We will see more about isolate scopes in the next chapter.
The controller $scope
are parameters that are sent to the controller and they get there through Dependency Injection (DI). The scope
of the link
function are parameters sent to link
and are standard order-based functions.
You can read more about the Directive Definition Object, Compile, and Link function on the official AngularJS API at https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object
Bootstrap user interface is very popular and is used by many web developers. The AngularJS community has their own in-built version on top of the Bootstrap JavaScript library, the AngularJS UI directives. However, using it is not always our first option; often, we need a simple solution.
In this recipe, we will see how to build component tabs without Angular UI.
Later in the book, we will see in depth how to use and customize Bootstrap UI directives. Now, we will focus on a simple directive tabs.
In a very basic way, we don't need to use a custom directive to build the tabs. So, let's see two ways to build a simple tab.
For the first example, we need the following code:
<!DOCTYPE html> <html ng-app="simpleTab"> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.4/angular.js"></script> <title>Simple tab</title> <style> .tabs-nav { padding: 20px 0 0; list-style: none; } .tabs-nav li { display: inline; margin-right: 20px; } .tabs-nav a { display:inline-block; cursor: pointer; } .tabs-nav .active { color: red; } .tab-content { border: 1px solid #ddd; padding: 20px; } </style> </head> <div class="tabs-holder" ng-app="simpleTab" ng-init="tab=1"> <ul class="tabs-nav"> <li><a ng-click="tab=1" ng-class="{'active' : tab==1}">First tab</a></li> <li><a ng-click="tab=2" ng-class="{'active' : tab==2}">Second tab</a></li> <li><a ng-click="tab=3" ng-class="{'active' : tab==3}">Third tab</a></li> </ul> <div class="tabs-container"> <div class="tab-content" ng-show="tab == 1"> <h1>First Tab</h1> <p>Simple tab 1</p> </div> <div class="tab-content" ng-show="tab == 2"> <h1>Second tab</h1> <p>Simple tab 2</p> </div> <div class="tab-content" ng-show="tab == 3"> <h1>Third Tab</h1> <p>Simple tab 3</p> </div> </div> </div> </body> </html>
For the second example, we need the following code. This time, we're using a controller and an external template. Place the following HTML code in a blank HTML file:
<!DOCTYPE html> <html ng-app="simpleTabController"> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.4/angular.js"></script> <title>Simple tab with Controller</title> <style> .tabs-nav { padding: 20px 0 0; list-style: none; } .tabs-nav li { display: inline; margin-right: 20px; } .tabs-nav a { display:inline-block; cursor: pointer; } .tabs-nav .active { color: red; } .tab-content { border: 1px solid #ddd; padding: 20px; } </style> </head> <body> <div class="tabs-holder" ng-app="simpleTabController"> <div id="tabs" ng-controller="TabsCtrl"> <ul class="tabs-nav"> <li ng-repeat="tab in tabs" ng-class="{active:isActiveTab(tab.url)}" ng-click="onClickTab(tab)">{{tab.title}}</li> </ul> <div id="tab-content"> <div ng-include="currentTab"></div> </div> <!--Script templates--> <script type="text/ng-template" id="first.html"> <div class="tab-content" id="1"> <h1>First Tab</h1> <p>Simple tab 1</p> </div> </script> <script type="text/ng-template" id="second.html"> <div class="tab-content" id="2"> <h1>Second Tab</h1> <p>Simple tab 2</p> </div> </script> <script type="text/ng-template" id="third.html"> <div class="tab-content" id="3"> <h1>Third Tab</h1> <p>Simple tab 3</p> </div> </script> </div> </div> </body> </html>
With the HTML already set up for both examples, let's dive into the controller's code for the second one. Add the following code to a separate file:
angular.module('simpleTabController', []) .controller('TabsCtrl', ['$scope', function ($scope) { $scope.tabs = [{ title: 'First tab', url: 'first.html' }, { title: 'Second tab', url: 'second.html' }, { title: 'Third tab', url: 'third.html' }]; $scope.currentTab = 'first.html'; $scope.onClickTab = function (tab) { $scope.currentTab = tab.url; } $scope.isActiveTab = function(tabUrl) { return tabUrl == $scope.currentTab; } }]);
The result of the tabs example is very similar to the following screenshot:

Simple tab layout example
Note that we keep the layout as simple as possible just for the example code.
For the second example, we keep the same stylesheet and layout. In both the examples, we include the CSS inside the head
element on the HTML page; you must avoid this on production applications.
The first example is pretty intuitive, and we only use the AngularJS built-in directives, such as ng-class
and ng-show
, to simulate the tab functionality.
<ul class="tabs-nav"> <li><a ng-click="tab=1" ng-class="{'active' : tab==1}">First tab</a></li> <li><a ng-click="tab=2" ng-class="{'active' : tab==2}">Second tab</a></li> <li><a ng-click="tab=3" ng-class="{'active' : tab==3}">Third tab</a></li> </ul>
Internally, the framework recognizes the reverse state of ng-show
and hides all the content of tabs 1 and 2. When we click on one of the other tabs, the state changes to show what has been clicked on and hides the others.
This is a simple example, but it is not very flexible.
In the second example, we added a controller to deal with the tabs logic, creating a $scope
to hold the tab title and their respective template:
$scope.tabs = [{ title: 'First tab', url: 'first.html' }, { title: 'Second tab', url: 'second.html' }, { title: 'Third tab', url: 'third.html' }];
We could easily introduce other elements in this array, such as description, date, and other elements, since we have loaded them from the controller. Although, it is possible to load the tabs content dynamically within this own array. We can also load the templates in external files, as we saw in the beginning of this chapter.
For this, transfer the contents of the script
tags (highlighted here) to external files, keeping the names as first.html
, second.html
, and third.html
:
<script type="text/ng-template" id="first.html"> <div class="tab-content" id="1"> <h1>First Tab</h1> <p>Simple tab 1</p> </div> </script>
Now just remove the script
tags from the original HTML file:
<script type="text/ng-template" id="second.html"> <div class="tab-content" id="2"> <h1>Second Tab</h1> <p>Simple tab 2</p> </div> </script> <script type="text/ng-template" id="third.html"> <div class="tab-content" id="3"> <h1>Third Tab</h1> <p>Simple tab 3</p> </div> </script>
Now we can have tabs with external templates.
These were simple examples for creation of tabs without using custom directives, and instead using built-in AngularJS directives. We highlighted the DOM manipulation's simplicity by using controllers rather than customized directives.
In addition to the previous examples, we can easily create a directive to use tabs. So, we address all the possibilities in the creation of this interactive component.
Let's see a directive example:
<!DOCTYPE html> <html > <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.4/angular.js"></script> <title>Simple tab with Directive</title> <style> .tabs-nav { padding: 20px 0 0; list-style: none; } .tabs-nav li { display: inline; margin-right: 20px; } .tabs-nav a { display:inline-block; cursor: pointer; } .tabs-nav .active { color: red; } .tab-content { border: 1px solid #ddd; padding: 20px; } </style> </head> <body> <div ng-app='simpleTabDirective'> <ng-tabs> <content-tab dat-heading='First tab' dat-tab-active> <h1>First Tab</h1> <p>Simple tab 1</p> </content-tab> <content-tab dat-heading='Second tab'> <h1>Second Tab</h1> <p>Simple tab 2</p> </content-tab> <content-tab dat-heading='Third tab'> <h1>Third Tab</h1> <p>Simple tab 3</p> </content-tab> </ng-tabs> </div> </body> </html>
Now, the controller turns into a directive:
var app = angular.module("simpleTabDirective", []) app.directive('ngTabs', function() { return { scope: true, replace: true, restrict: 'E', transclude: true, template: ' \ <div class="tab-content"> \ <ul class="tabs-nav"> \ <li ng-repeat="tab in tabs" \ ng-class="{ active: currentTab == $index }"> \ <a ng-click="selectTab($index)"> \ {{tab}} \ </a> \ </li> \ </ul> \ <div ng-transclude></div> \ </div>', controller: function($scope) { $scope.currentTab = 0; $scope.tabs = []; $scope.selectTab = function(index) { $scope.currentTab = index; }; return $scope; } } }) app.directive('contentTab', function() { return { require: '^ngTabs', restrict: 'E', transclude: true, replace: true, scope: true, template: '<div class="tab-content" ng-show="showTab()" ng-transclude></div>', link: function(scope, element, attrs, ngTabs) { var tabId = ngTabs.tabs.length; scope.showTab = function() { return tabId == ngTabs.currentTab; }; ngTabs.tabs.push(attrs.datHeading); } } });
Note that we use the property require
to set the dependence of ngTabs
. In this way, our tab consists of two directives, one to create the list where we will have the title of the tabs and the second to create the contents of each tab itself. The code is as follows:
<ng-tabs> <content-tab dat-heading='First tab'> </content-tab> </ng-tabs>
We can also observe that we have used all the features seen earlier in this chapter, such as ng-click
, ng-repeat
, and ng-transclude
, among others.
A great resource that helps us in search of directives, and other related stuff, for the development of applications with AngularJS is the website Angular Modules (http://ngmodules.org/tags/directive)