Custom Directives in AngularJS

This article explains custom directives.

Directives are the most important components of any AngularJS application. Directives are markers on a DOM element that tell angular js HTML compiler to attach specific behaviour to that element. We know the use of some commonly used built-in directives like ng-app, ng-controller, ng-repeat, ng-show, ng-hide and so on. All these directives attach special behaviour to DOM elements.

Although AngularJS ships with a wide range of directives, you will often need to create application-specific directives since some of the requirements in the application requirements vary. The custom directive helps the developer in creating a reusable component that can be maintained easily. Consider the example of a custom control or web user control in ASP.Net web applications or HTML helpers in ASP.Net MVC applications. Very similar to that, custom directives are UI components built in angular js.

Angular js has its own way of manipulating the DOM element, it is not the same way as in jQuery. It is the directive where we need to do the DOM Manipulation. Don't mix up jQuery with angular js it will create a mess up code which is not a good practice.

Custom directives are used in AngularJS to extend the functionality of HTML. Custom directives are defined using the "directive" function. A custom directive simply replaces the element for which it is activated. An AngularJS application during bootstrap finds the matching elements defined in the custom directive and does a one-time activity using its compile() method of the custom directive then processes the element using the link() method of the custom directive based on the scope of the directive. AngularJS provides support to create custom directives for the following types of elements.

AngularJS Directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tells the AngularJS HTML compiler ($compile) to attach a specified behaviour to that DOM element or even transform the DOM element and its children.

Custom directives in AngularJS is your own directive with their own core functions that run when the DOM is compiled by the compiler. Suppose we want to reuse a specific lines of code in multiple pages of our application without code duplication, then we simply put that specific piece of code in a separate file and call that code using a custom directive instead of writing it over and over again. This way code is better to understand.

Let us look at the syntax of creating a custom directive as in the following:

 

  1. myModule.directive("directiveName"function(injectables component)   
  2. {  
  3.     return   
  4.     {  
  5.         restrict: "A/E/C/M"//Attribute Element Class Comment  
  6.         template: "<div></div>"// Html to be added  
  7.         templateUrl: "directive.html"// template to be added  
  8.         replace: false// replace yes/No  
  9.         transclude: false// Transclude yes/No  
  10.         scope: false// Current scope or isolated scope  
  11.         require: ["someOtherDirective"], // if any other directive is needed  
  12.         controller: function($scope, $element, $attrs, $transclude, otherInjectables) {...  
  13.         }, // controller  
  14.         link: function postLink(scope, iElement, iAttrs) {...  
  15.         },  
  16.         priority: 0, //Priority of directive  
  17.         terminal: false,  
  18.         compile: function compile(tElement, tAttrs, transclude)   
  19.         {  
  20.             return   
  21.             {  
  22.                 pre: function preLink(scope, iElement, iAttrs, controller) {...  
  23.                 },  
  24.                 post: function postLink(scope, iElement, iAttrs, controller) {...  
  25.                 } // compile functions  
  26.             }  
  27.         }  
  28.     };  
  29. });  
When writing a custom directive we don't need all the parameters, we need some of them depending on our needs. Let us understand what these parameters are and what role they play in custom directives.
 
restrict: Specifies how a directive can be used. Possible values are: E (element), A (Attribute), C (Class) and M (Comment). The default value is A. This provides a way to specify how a directive should be used in HTML (remember a directive can appear in four ways). In this case we have set it to "AE". So, the directive can be used as a new HTML element or an attribute. To allow this directive to be used as a class we can set restrict to "AEC".

template: HTML template to be rendered in the custom directive. This specifies the HTML markup that will be produced when the directive is compiled and linked by Angular codes. This does not need to be a simple string. The template can be complex, often involving other directives, expressions ({{ }}) and so on. In most cases you want to use templateUrl instead of template. So, ideally you should place the template in a separate HTML file and make templateUrl point to it.

templateUrl: URL of the file containing HTML template of the element.

replace: Boolean value denoting if the directive element is to be replaced by the template. The default value is false.

transclude: Boolean value that says if the directive should preserve the HTML specified inside the directive element after rendering. The default is false. Let's understand a bit more on transclude.

Example of transclude

Consider a directive called myDirective in an element and that element is enclosing some other content, let's say:

 

  1. <div my-directive>  
  2.    <button>some button</button>  
  3.    <a href="#">and a link</a>  
  4. </div>  

If myDirective is using a template, you'll see that the content of <div my-directive> will be replaced by your directive template. So having:

 

  1. app.directive('myDirective'function()   
  2. {  
  3.     return   
  4.     {  
  5.         template: '<div class="something"> This is my directive content</div>'  
  6.     }  
  7. });  

This will result in this render:

 

  1. <div class="something"> This is my directive content</div>  

Observe here, the content of your original element <div my-directive> will be lost.

 

  1. <button>some button</button>  
  2. <a href="#">and a link</a>  

So, what if you want to keep your page as in the following:

  1. <button>some button</button>  
  2. <a href="#">and a link</a>  
You'll need something called transclusion.

Include the content from one place into another. So now your directive will look something like this:

  1. app.directive('myDirective', function()   
  2. {  
  3.     return   
  4.     {  
  5.         transclude: true,  
  6.         template: '<div class="something" ng-transclude> This is my directive content</div>'  
  7.     }  
  8. });  
This would render:

 

  1. <div class="something"> This is my directive content  
  2.    <button>some button</button>  
  3.    <a href="#">and a link</a>  
  4. </div>  

Scope: Scope of the directive. It may be the same as the scope of the surrounding element (default or when set to false), inherited from the scope of the surrounding element (set to true) or an isolated scope (set to {}).

By default, a directive gets the parent's scope. But we don't want that in all cases. If we are exposing the parent controller's scope to the directives, they are free to modify the scope properties. In some cases your directive may want to add several properties and functions to the scope that are for internal use only. If we are doing these things to the parent's scope, we are polluting it. So, we have the following two other options:

  • A child scope: This scope prototypically inherits the parent's scope.
  • An isolated scope: A new scope that does not inherit from the parent and exists on its own.
In Angular, this binding can be done by setting attributes on the directive element in HTML and configuring the scope property in the directive definition object.

Let's explore a few ways of setting up the binding.

Option 1: Use @ for One Way Text Binding

Binds the value of parent scope property (that is always a string) to the local scope. So the value you want to pass in should be wrapped in {{}}.

Option 2: Use = for two-way binding and binds the Parent scope property directly that will be evaluated before being passed in.

Option 3: Use and to Execute functions in the parent scope and binds an expression or method that will be executed in the context of the scope it belongs in.

  1. app.directive('helloWorld'function()  
  2. {  
  3.     return   
  4.     {  
  5.         scope: {  
  6.             Name: '='  
  7.             Name: '@'  
  8.             Name: '='  
  9.         },  
  10.         // other codes go here  
  11.     };  
  12. });  
By default a directive does not create a new scope and uses the parent's scope. But in many cases this is not what we want. If your directive manipulates the parent scope properties heavily and creates new ones, it might pollute the scope. Letting all the directives use the same parent scope is not a good idea because anybody can modify our scope properties. So, the following guidelines may help you choose the right scope for your directive.

Parent Scope (scope: false): This is the default case. If your directive does not manipulate the parent scope properties you might not need a new scope. In this case, using the parent scope is okay.

Child Scope (scope:true): This creates a new child scope for a directive that prototypically inherits from the parent scope. If the properties and functions you set on the scope are not relevant to other directives and the parent, you should probably create a new child scope. With this you also have all the scope properties and functions defined by the parent.

Isolated Scope (scope:{}): This is like a sandbox! You need this if the directive you will build is self-contained and reusable. Your directive might be creating many scope properties and functions that are meant for internal use and should never be seen by the outside world. If this is the case, it's better to have an isolated scope. The isolated scope, as expected, does not inherit the parent scope.

require: A list of directives that the current directive needs. The current directive gets access to controller of the required directive. An object of the controller is passed into the link function of the current directive

controller: Controller for the directive. Can be used to manipulate values on scope or as an API for the current directive or a directive requiring the current directive.

priority: Sets the priority of a directive. The default value is 0. A directive with a higher priority value is executed before a directive with a lower priority.

terminal: Used with priority. If set to true, it stops execution of directives with lower priority. Default is false.

link: A function that contains the core logic of the directive. It is executed after the directive is compiled. Gets access to the scope and element on which the directive is applied (jqLite object attributes) of the element containing the directive and controller object. Generally used to perform DOM manipulation and handling events.

compile: A function that runs before the directive is compiled. Doesn't have access to the scope since the scope is not created yet. Gets an object of the element and attributes. Used to perform the DOM of the directive before the templates are compiled and before the directive is transcluded. It returns an object with the following two link functions:

pre link: Similar to the link function, but it is executed before the directive is compiled. By this time, transclusion is applied. Used rarely. One of the use cases is when a child directive requires data from its parent, the parent directive should set it through its pre-link function. Set data required for its child directives. Safe to attach event handlers to the DOM element. Not safe to access DOM elements belonging to child directives. They're not linked yet.

post link: This is the most commonly used for data binding. Safe to attach event handlers to the DOM element. All children directives are linked, so it's safe to access them. Never set any data required by the child directive here, because the child directive's will be linked already.

Compilation in directives

When the application bootstraps, Angular starts parsing the DOM using the $compile service. This service searches for directives in the mark-up and matches them against registered directives. Once all the directives have been identified, Angular executes their compile functions. As previously said, the compile function returns a link function that is added to the list of link functions to be executed later. This is called the compile phase. If a directive needs to be cloned multiple times (for example ng-repeat), we get a performance benefit since the compile function runs once for the cloned template, but the link function runs for each cloned instance. That's why the compile function does not receive a scope.

After the compile phase is over, next is the linking phase, where the collected link functions are executed one by one. This is where the templates produced by the directives are evaluated against correct scope and are turned into a live DOM that reacts to events.

Let's do some simple samples on custom directives now.

Example-1

We will create a directive called "firstDirective" that will return us a sample text.

 

  1. <!DOCTYPE html>  
  2. <html ng-app="myApp">  
  3.     <head>  
  4.         <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.min.js"></script>  
  5.         <script>  
  6.   
  7. var myapp = angular.module('myApp', [])  
  8.   
  9. myapp.directive('firstDirective', function () {  
  10.   
  11. return {  
  12.   
  13. template: '  
  14.             <h3> I am From custom directive !!! </h3>'  
  15.   
  16. };  
  17.   
  18. });  
  19.         </script>  
  20.     </head>  
  21.     <body>  
  22.         <div ng-app="myApp">  
  23.             <div>  
  24.                 <div>  
  25.                     <first-directive>  
  26.                 </div>  
  27.             </div>  
  28.         </div>  
  29.     </body>  
  30. </html>  
Note: "firstDirective" name must be a camel case while declaring a directive and while calling it should be written as <first-directive> . There are some other way of calling it as in the following:

<first_directive> or <first:directive>

Output below

 

Example-2

We will create a directive that will convert the first letter of words to uppercase.

 

  1. <!DOCTYPE html>  
  2. <html ng-app="myApp">  
  3. <head>  
  4. <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.min.js"></script>  
  5. <script>  
  6. var myApp = angular.module('myApp', []);  
  7. myApp.directive('capitalizeFirst', function (uppercaseFilter, $parse) {  
  8. return {  
  9. require: 'ngModel',  
  10. link: function (scope, element, attrs, modelCtrl) {  
  11. var capitalize = function (inputValue) {  
  12. var capitalized = inputValue.charAt(0).toUpperCase() +  
  13. inputValue.substring(1);  
  14. if (capitalized !== inputValue) {  
  15. modelCtrl.$setViewValue(capitalized);  
  16. modelCtrl.$render();  
  17. }  
  18. return capitalized;  
  19. }  
  20. var model = $parse(attrs.ngModel);  
  21. modelCtrl.$parsers.push(capitalize);  
  22. capitalize(model(scope));  
  23. }  
  24. };  
  25. });  
  26. </script>  
  27.     </head>  
  28.     <body>  
  29.         <div>  
  30.             <input type="text" ng-model="name" capitalize-first>  
  31.         </div>  
  32.     </body>  
  33. </html>   
Output
 
 

Example 3

In the third example we will create a directive that will define some extra “USD” to text on the blur event.

 

  1. <!DOCTYPE html>  
  2. <html ng-app="app">  
  3.     <head>  
  4.         <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.min.js"></script>  
  5.         <script>  
  6. var app = angular.module('app', []);  
  7. app.controller('currencyController', ['$scope', function (scope) {  
  8. scope.amount = 5;  
  9. }]).directive('srCurrency', ['$filter', function ($filter) {  
  10. return {  
  11. restrict: 'A',  
  12. require: 'ngModel',  
  13. link: function (scope, el, attrs, ngModel) {  
  14. function onChangeCurrency() {  
  15. ngModel.$setViewValue($filter('currency')(ngModel.$viewValue, 'USD$'));  
  16. ngModel.$render();  
  17. el.css('color', 'blue');  
  18. }  
  19. el.on('blur', function (e) {  
  20. scope.$apply(onChangeCurrency);  
  21. });  
  22. }  
  23. }  
  24. }]);  
  25. </script>  
  26.     </head>  
  27.     <body>  
  28.         <div ng-app='app' ng-controller="currencyController">  
  29.         Enter The Expense Amount :  
  30.             <input type="text" sr-currency ng-model='amount' />  
  31.         </div>  
  32.     </body>  
  33. </html>  
Output
 
 

Example 4

Let us do a directive that will read the value from the scope object and show it in a tabular format.

 

  1. <!DOCTYPE html>  
  2. <html ng-app="myApp">  
  3.     <head>  
  4.         <style type="text/css">  
  5. table {  
  6. color:#666;  
  7. text-shadow: 1px 1px 0px #fff;  
  8. background:#eaebec;  
  9. margin:20px;  
  10. border:#ccc 1px solid;  
  11. -moz-border-radius:3px;  
  12. -webkit-border-radius:3px;  
  13. border-radius:3px;  
  14. -moz-box-shadow: 0 1px 2px #d1d1d1;  
  15. -webkit-box-shadow: 0 1px 2px #d1d1d1;  
  16. box-shadow: 0 1px 2px #d1d1d1;  
  17. }  
  18. table th {  
  19. padding:21px 25px 22px 25px;  
  20. border-top:1px solid #fafafa;  
  21. border-bottom:1px solid #e0e0e0;  
  22. background: #ededed;  
  23. background: -webkit-gradient(linear, left top, left bottom, from(#ededed), to(#ebebeb));  
  24. background: -moz-linear-gradient(top, #ededed, #ebebeb);  
  25. }  
  26. table th:first-child {  
  27. text-align: left;  
  28. padding-left:20px;  
  29. }  
  30. table tr:first-child th:first-child {  
  31. -moz-border-radius-topleft:3px;  
  32. -webkit-border-top-left-radius:3px;  
  33. border-top-left-radius:3px;  
  34. }  
  35. table tr:first-child th:last-child {  
  36. -moz-border-radius-topright:3px;  
  37. -webkit-border-top-right-radius:3px;  
  38. border-top-right-radius:3px;  
  39. }  
  40. table tr {  
  41. text-align: center;  
  42. padding-left:20px;  
  43. }  
  44. table td:first-child {  
  45. text-align: left;  
  46. padding-left:20px;  
  47. border-left: 0;  
  48. }  
  49. table td {  
  50. padding:18px;  
  51. border-top: 1px solid #ffffff;  
  52. border-bottom:1px solid #e0e0e0;  
  53. border-left: 1px solid #e0e0e0;  
  54. background: #fafafa;  
  55. background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#fafafa));  
  56. background: -moz-linear-gradient(top, #fbfbfb, #fafafa);  
  57. }  
  58. table tr.even td {  
  59. background: #f6f6f6;  
  60. background: -webkit-gradient(linear, left top, left bottom, from(#f8f8f8), to(#f6f6f6));  
  61. background: -moz-linear-gradient(top, #f8f8f8, #f6f6f6);  
  62. }  
  63. table tr:last-child td {  
  64. border-bottom:0;  
  65. }  
  66. table tr:last-child td:first-child {  
  67. -moz-border-radius-bottomleft:3px;  
  68. -webkit-border-bottom-left-radius:3px;  
  69. border-bottom-left-radius:3px;  
  70. }  
  71. table tr:last-child td:last-child {  
  72. -moz-border-radius-bottomright:3px;  
  73. -webkit-border-bottom-right-radius:3px;  
  74. border-bottom-right-radius:3px;  
  75. }  
  76. table tr:hover td {  
  77. background: #f2f2f2;  
  78. background: -webkit-gradient(linear, left top, left bottom, from(#f2f2f2), to(#f0f0f0));  
  79. background: -moz-linear-gradient(top, #f2f2f2, #f0f0f0);  
  80. }  
  81. .odd {  
  82. color: red;  
  83. }  
  84. .even {  
  85. color: blue;  
  86. }  
  87. </style>  
  88.         <script src="scripts/angular.min.js"></script>  
  89.         <script>  
  90. var myapp = angular.module('myApp', [])  
  91. .controller('countryctrl', ['$scope', function ($scope) {  
  92. $scope.data = [{  
  93. id: 1,  
  94. name: 'India',  
  95. capital: 'New Delhi',  
  96. curreny:"INR"  
  97. }, {  
  98. id: 2,  
  99. name: 'USA',  
  100. capital: 'New York',  
  101. curreny: "USD"  
  102. }, {  
  103. id: 3,  
  104. name: 'China',  
  105. capital: ' Beijing ',  
  106. curreny: "Renminbi"  
  107. }, {  
  108. id: 4,  
  109. name: 'Japan',  
  110. capital: 'Tokyo',  
  111. curreny: "yen"  
  112. }, {  
  113. id: 5,  
  114. name: 'Korea',  
  115. capital: 'Seoul',  
  116. curreny: "Won "  
  117. }, {  
  118. id: 6,  
  119. name: 'Vietnam',  
  120. capital: 'Hanoi',  
  121. curreny: "dong"  
  122. }  
  123. ];  
  124. }])  
  125. .directive('trRow', function () {  
  126. return {  
  127. template: '  
  128.             <tr>  
  129.                 <td ng-bind="row.id"></td>  
  130.                 <td>  
  131.                     <strong ng-bind="row.name"></strong>  
  132.                 </td>  
  133.                 <td ng-bind="row.capital"></td>  
  134.                 <td ng-bind="row.curreny"></td>  
  135.             </tr>'  
  136. };  
  137. });  
  138. </script>  
  139.     </head>  
  140.     <body>  
  141.         <div ng-app="myApp">  
  142.             <table ng-controller="countryctrl">  
  143.                 <thead style="color:red; ">  
  144.                     <th>Country Id</th>  
  145.                     <th>Country Name</th>  
  146.                     <th>Capital</th>  
  147.                     <th>Currency</th>  
  148.                 </thead>  
  149.                 <tbody>  
  150.                     <tr tr-row ng-repeat="row in data"></tr>  
  151.                 </tbody>  
  152.             </table>  
  153.         </div>  
  154.     </body>  
  155. </html>  
Output
 
 

These are some basic directive examples to start custom directives. In an application, if you observe, there can be many reusable UI components that we can make it a custom directive and make it usable across the team to use it.

In the next article I will return with more on custom directives. I hope you now have a understanding of custom directives. Thanks for reading!