AngularJs directives are used for extending HTML. AngularJS provides several built-in directives like ng-controller, ng-repeat, ng-app, ng-model, ng-show, ng-hide, ng-cloak etc. But sometimes we will come to the point where built- in directives are not enough for our need. In those cases, we can create custom directives.
Creating a Custom Directive:
AngularJs module's directive is function used to create custom directives
Example:
angular.module('myAppDirective', []).directive('itemInfo',[function() {
return { //returns Directive Definition Object.
// Directive definition goes here.
};
}]);
The directive function contains two arguments, the first argument is directive's name itself and the second argument is factory function which is used for configuring our directive. This factory function returns Directive Definition Object. Once the declaration of the directive is completed, you can use it in HTML. The directive can be used as an element of HTML, an attribute of an existing element in HTML, CSS class or comment. On every use of directive in HTML, AngularJs will refer the Directive Definition Object.
Directive naming in HTML:
In above example, name of the directive is itemInfo. When you are using this itemInfo directive in your HTML, you can use it in 3 ways like item-info or item:info or item_info. AngularJs normalize the directive name which is in HTML to match it with JavaScript name. It removes the prefix (-, : , _) from directive name and converts the prefix separated name to camelCase , for example, item-info in HTML gets converted into itemInfo to match the JavaScript name.
Directive Definition Object:
Directive Definition Object contains various configuration options, for example template, templateUrl, restricts, scope etc. Now we will discuss each one of them in detail.
- template and templateUrl:
template and templateUrl are used in the case where you need to reuse some HTML snippet in your application. If the directive contents are small then the template is a preferable option. templateUrl is preferable when directive contents are large. tempalteUrl is the path of the file which contains the contents of directive.
Example 1.1: Using templateUrl
<html>
<head>
<title>Custom Directive Demo</title>
</head>
<body ng-app="myAppDirective">
<div ng-controller="DirectiveDemoController as ctrl">
<h3>List of Sale Items</h3>
<div ng-repeat="item in ctrl.itemList">
<div item-info></div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js">
</script>
<script>
angular.module('myAppDirective', [])
.controller('DirectiveDemoController', [function() {
var self = this;
self.itemList = [
{name: 'Computer', price: 500, brand : 'Lenovo', published:'01/11/2015'},
{name: 'Phone', price: 200, brand : 'Samsung', published:'02/11/2015'},
{name: 'Printer', price: 300, brand : 'Brother', published:'06/11/2015'},
{name: 'Dishwasher', price: 250, brand : 'WhirlPool', published:'01/12/2015'},
];
}])
.directive('itemInfo', [function() {
return{
templateUrl: 'itemList.html'
}
}]);
</script>
</body>
</html>
Below shown is 'itemList.html' which contains content of directive 'itemInfo'.
<div>
<div>
Published at :<span ng-bind="item.published | date"></span>
</div>
<div>
Name:<span ng-bind="item.name"></span>
Price:<span ng-bind="item.price | currency"></span>
Brand:<span ng-bind="item.brand"></span>
</div>
</div>
In above example, we have declared directive 'itemInfo'. We are using templateUrl key in directive declaration, which points to file 'itemList.html' containing content of directive 'itemInfo'. Also we are using this directive ('itemInfo') as an attribute of <div></div> element within ng-repeat loop, passing items from the controller.
Using template:
<html>
<head>
<title>Custom Directive Demo</title>
</head>
<body ng-app="myAppDirective">
<div ng-controller="DirectiveDemoController as ctrl">
<h3>List of Sale Items</h3>
<div ng-repeat="item in ctrl.itemList">
<div item-info></div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js">
</script>
<script>
angular.module('myAppDirective', [])
.controller('DirectiveDemoController', [function() {
var self = this;
self.itemList = [
{name: 'Computer', price: 500, brand : 'Lenovo', published:'01/11/2015'},
{name: 'Phone', price: 200, brand : 'Samsung', published:'02/11/2015'},
{name: 'Printer', price: 300, brand : 'Brother', published:'06/11/2015'},
{name: 'Dishwasher', price: 250, brand : 'WhirlPool', published:'01/12/2015'},
];
}])
.directive('itemInfo', [function() {
return{
template: '<div >'+
'<div >'+'Published at:<span ng-bind="item.published |date"></span>'+
'</div>'+
'<div >'+
'Name:<span ng-bind="item.name"></span>'+
'Price:<span ng-bind="item.price | currency"></span>'+
'Brand:<span ng-bind="item.brand"></span>'+
'</div>'+
'</div>'
}
}]);
</script>
</body>
</html>
In above example we are using template key of directive for declaring directive's content.
2. restrict:
AngularJs Directive's restrict configuration option defines the way in which we can use our directive in HTML. The possible values for restrict option are listed below:
A: Specifies that directive can be used as an attribute of existing HTML element, like <div item-info><div>. By default, directive can only be used as an attribute of existing element in HTML when restrict is not defined.
E: Specifies that directive will be used as an element, like <item-info><item-info>.
C: Specifies that directive will be used as a class name in existing HTML element, for example: <div class="item-info"></div>.
M: Specifies that directive will be used as HTML comments, like <! – Using directive: item-widget–>.
You can combine these keys. But preferred options are A & E types.
If we want to use our directive as HTML element, we should just declare restrict key with value ‘E', like following configuration:
.directive('itemInfo', [function() {
return{
templateUrl:'itemList.html',
restrict:'E'
}
}]);
Once you declare restrict key with value ‘E' and then try to use item-info as an attribute, AngularJS will throw an error. If we want to allow item-info to use both as element and attribute, we can do this using following configuration:
.directive('itemInfo', [function() {
return{
templateUrl:'itemList.html',
restrict:'EA'
}
}]);
3. scope:
Using scope configuration option of directive, we can create directive's isolated scope. By Creating isolated scope, directive's inner scope gets separated from parent scope .This way, we can achieve decoupling of directive’s scope from parent scope. Decoupling makes directive independent and can be reusable without relying on specific properties/functions of the parent. Directive's scope does not inherit anything from parent scope.
Let's understand this concept with help of example.
In above Example 1.1 directive's inner scope is tightly coupled to parent scope, this seems bad coding practice. As you can see, in directive content [itemList.html], we are using an object ‘item' which is coming from parent scope:
<div ng-repeat="item in ctrl.items">
<item-info></item-info>
</div>
Here, ng-repeat creates a scope for each item in an array of the controller. If we change above ng-repeat to the following, our directive will break. Because it looks for ‘item' variable in its content [itemList.html].
<div ng-repeat="itemInformation in ctrl.items">
<item-info></item-info>
</div>
To break this tight coupling, we have to separate out the directive's inner scope from the parent scope. For this purpose we create a directive’s isolated scope using directive's scope key. This will make directive independent, and then map the parent scope to a directive's inner scope to get any parameter we may need in directive's content. Refer the following configuration:
.directive('itemInfo', [function() {
return{
restrict: 'E',
scope: {
item: '=',
promoMessage: '@',
clickMe : '&onSelect'
},
templateUrl: 'itemList.html',
}
}]);
scope key is an object which contains a property for each isolated scope binding.
- In above configuration item, promoMessage, clickMe are the directive’s isolate scope properties. This configuration tells AngularJS $compile to bind to the item, promoMessage, clickMe attributes in HTML.
Below shown HTML side code:
<div ng-repeat="itemInformation in ctrl.items">
<item-info item=" itemInformation” promoMessage ="Diwali-Sale" ></item-info>
</div>
Here, we have mapped parent scope’s variable ‘itemInformation ‘and string ‘Diwali-Sale’ to directive’s scope variables ‘item’ and ‘promoMessage’ respectively.
Now, we will discuss about ‘=’,’@’ and ’&’ these 3 special characters, which we have used in our directive configuration.
- = character specifies that the value passed in directive’s attribute in HTML is an object.
- @ is preferred when you want to pass string as value in directive’s attribute in HTML.
- & specifies that value passed in directive’s attribute in HTML is a function in some controller.
4. link:
link is also configuration key in AngularJs directives. Using link option we can define link function for directive. Link function contains definition of directive’s API and directives use this function to perform some business logic. It is also useful for updating the DOM.
For each instance of directive link function gets executed so that each instance of directive get’s its own business-logic without affecting others. Below shown is list of various arguments to link function:
link: function(scope, element, attributes) {
...
}
- scope : scope of the element on which directive is applied.
- element : It is the DOM element in HTML on which directive is applied.
- attributes: Llist of attributes as string available on the element in HTML.
Example: Using link configuration option.
<html>
<head>
<title>Custom Directive Demo</title>
</head>
<body ng-app="myAppDirective">
<div ng-controller="DirectiveDemoController as ctrl">
<h3>List of Sale Items.</h3>
<div ng-repeat="itemInformation in ctrl.items">
<item-info item="itemInformation" promoMessage="Diwali-Sale" on-select="ctrl.onItemSelection(selectedItem)"></item-info>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js">
</script>
<script>
angular.module('myAppDirective', [])
.controller('DirectiveDemoController', [function() {
var self = this;
self.itemList = [
{name: 'Computer', price: 700, brand : 'Lenovo', published:'01/11/2016'},
{name: 'Phone', price: 500, brand : 'Samsung', published:'02/11/2016'},
{name: 'Printer', price: 800,brand : 'Brother', published:'06/11/2016'},
{name: 'Dishwasher', price: 350,brand : 'WhirlPool', published:'01/12/2016'},
];
self.onItemSelection = function(itemName) {
console.log('Congrats. You have just bought a', itemName);
};
}])
.directive('itemInfo', [function() {
return{
restrict: 'EA',
scope: {
item: '=',
promoMessage: '@',
clickMe : '&onSelect'
},
templateUrl: 'itemList.html',
link : function(scope, element, attributes){
//Add event listener for 'click' event
element.on('click', function(event) {
element.html('Thank you for buying this item.');
element.css({
color: 'orange'
});
});
}
}
}]);
</script>
</body>
</html>
Below shown is the file itemList.html which contains directive’s contents:
<div >
<div >
Published at:<span ng-bind="item.published | date"></span> Promotion: <button class="pull-right" ng-click="clickMe({selectedItem:item.itenName})">Buy Item</button>
</div>
<div >
Name:<span ng-bind="item.name"></span>
Price:<span ng-bind="item.price | currency"></span>
Brand:<span ng-bind="item.brand"></span>
</div>
</div>