While working on one of the applications being developed on the AngularJS framework, we came across a situation where we had to add a reusable and independent functionality on a DOM element. This could easily be done using jQuery. However, since the app was being developed using AngularJS, the best practice was to stick to that only, which was made easy with the AngularJS “Directive” tool.
What are Custom Directives?
$compile
) to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform the DOM element and its children.ngBind
, ngModel
, and ngClass
. Much like you create controllers and services, you can create your own directives for AngularJS to use. When AngularJS bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements.Normalization
ngModel
). However, since HTML is case-insensitive, we refer to directives in the DOM by lower-case forms, typically using dash-delimited attributes on DOM elements (e.g. ng-model
).- Strip
x-
anddata-
from the front of the element/attributes. - Convert the
:
,-
, or_
-delimited name tocamelCase
.
ngBind
directive:<div ng-controller="Controller">
Hello <input ng-model='name'> <hr/>
<span ng-bind="name"></span> <br/>
<span ng:bind="name"></span> <br/>
<span ng_bind="name"></span> <br/>
<span data-ng-bind="name"></span> <br/>
<span x-ng-bind="name"></span> <br/>
</div>
Directive types
$compile
can match directives based on element names (E), attributes (A), class names (C), and comments (M).myDir
in this case) that matches all 4 types can be referenced from within a template.<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>
A directive can specify which of the 4 matching types it supports in the restrict
property of the directive definition object. The default is EA
.
Defining a Directive
This section lists simple steps to define a custom directive in an AngularJS module. First we need to define an AngularJS app then our own custom directive named “myCustomer“.
angular.module('myApp', [])
.controller('MyController', ['$scope', function($scope) {
$scope.customer = {
name: 'Anil',
address: 'Hyderabad'
};
}])
.directive('myCustomer', function() {
return {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
};
});
<div ng-app="myApp" ng-controller="Controller">
<div my-customer></div>
</div>
Syntax of a Directive
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'src/app/component/landing/landing.directive.html',
scope:{
'name': '=',
'address': '@address',
'onClick': '&onClick'
},
compile: function(tElement, tAttrs, transclude){
},
link: function(scope, iElement, iAttrs, controllerAs, transcludeFn){
},
controller: function($scope, $rootScope){
}
};
});
Properties wise details
templateUrl
can also be a function which returns the URL of an HTML template to be loaded and used for the directive. AngularJS will call thetemplateUrl
function with two parameters: the element that the directive was called on, and anattr
object associated with that element.-
The
restrict
option is typically set to:'A'
– only matches attribute name'E'
– only matches element name'C'
– only matches class name'M'
– only matches comment
These restrictions can all be combined as needed:'AEC'
– matches either attribute or element or class name
-
scope: What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive’s inner scope. We can do this by creating what we call an isolate scope. To do this, we can use a directive’s scope option.
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
templateUrl: 'my-customer-iso.html'
};
});
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
templateUrl: 'my-customer-iso.html'
};
});
- Its name (
customerInfo
) corresponds to the directive’s isolate scope property,customerInfo
. - Its value (
=info
) tells$compile
to bind to theinfo
attribute. - (=info) binds both way between two components(controller to directive and vice-versa).
- (@info) binds one way from controller to directive.
- (&info) binds one way from directive to controller.
4. Link
link
option to register DOM listeners well as update the DOM. It is executed after the template has been cloned and is where directive logic will be put.link
takes a function with the following signature, function link(scope, element, attrs, controller, transcludeFn) { ... }
, where:scope
is an AngularJS scope object.element
is the jqLite-wrapped element that this directive matches.attrs
is a hash object with key-value pairs of normalized attribute names and their corresponding attribute values.controller
is the directive’s required controller instance(s) or its own controller (if any). The exact value depends on the directive’s require property.transcludeFn
is a transclude linking function pre-bound to the correct transclusion scope.
myApp.directive('myDirective', function() {
return {
restrict: 'E',
link: function(scope, element, attr){
element.append("<strong>"+attr.title+"</strong>");
}
};
});
<div ng-app="myApp">
<my-directive title="Hello Friends"></my-directive>
</div>
5. Compile
function compile(tElement, tAttrs, transclude) { ... }
-
tElement
– template element – The element where the directive has been declared. It is safe to do template transformation on the element and child elements only. -
tAttrs
– template attributes – Normalized list of attributes declared on this element shared between all directive compile functions. -
transclude
– [DEPRECATED!] A transclude linking function:function(scope, cloneLinkingFn)
6. Controller
Controller constructor function. The controller is instantiated before the pre-linking phase and can be accessed by other directives (seerequire
attribute). This allows the directives to communicate with each other and augment each other’s behavior. The controller is injectable (and supports bracket notation) with the following locals:
$scope
– Current scope associated with the element$element
– Current element$attrs
– Current attributes object for the element
angular.module('docsTabsExample', [])
.directive('myTabs', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: ['$scope', function MyTabsController($scope) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
};
this.addPane = function(pane) {
if (panes.length === 0) {
$scope.select(pane);
}
panes.push(pane);
};
}],
templateUrl: 'my-tabs.html'
};
})
.directive('myPane', function() {
return {
require: '^^myTabs',
restrict: 'E',
transclude: true,
scope: {
title: '@'
},
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
},
templateUrl: 'my-pane.html'
};
});
<my-tabs>
<my-pane title="Hello">
<p>Lorem ipsum dolor sit amet</p>
</my-pane>
<my-pane title="World">
<em>Mauris elementum elementum enim at suscipit.</em>
<p><a href ng-click="i = i + 1">counter: {{i || 0}}</a></p>
</my-pane>
</my-tabs>
<div class="tabbable">
<ul class="nav nav-tabs">
<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">
<a href="" ng-click="select(pane)">{{pane.title}}</a>
</li>
</ul>
<div class="tab-content" ng-transclude></div>
</div>
<div class="tab-pane" ng-show="selected">
<h4>{{title}}</h4>
<div ng-transclude></div>
</div>
7. Transclude
transclude
option do, exactly? transclude
makes the contents of a directive with this option have access to the scope outside of the directive rather than inside.link
function in script.js
that redefines name
as Jeff
. What do you think the {{name}}
binding will resolve to now?angular.module('myApp', [])
.controller('MyController', ['$scope', function($scope) {
$scope.name = 'Tobias';
}])
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
templateUrl: 'my-dialog.html',
link: function(scope) {
scope.name = 'Jeff';
}
};
});
<div ng-controller="MyController">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
{{name}}
would be Jeff
. However, we see in this example that the {{name}}
binding is still Tobias
.transclude
option changes the way scopes are nested. It makes it so that the contents of a transcluded directive have whatever scope is outside the directive, rather than whatever scope is on the inside. In doing so, it gives the contents access to the outside scope.scope
in scope.name = 'Jeff'
would reference the outside scope and we would see Jeff
in the output.Advantages
Directives are useful in creating repeatable and independent code. They modularize the code by clubbing requirement-specific behavioral functions in one place, rather than creating some objects in the central controller and manipulating them using multiple JavaScript methods. Such a modular code will have multiple directives that handle their own functionalities and data, and work in isolation from other directives. As an added benefit, the HTML page and Angular scripts becomes less messy and more organized.