Creating Custom Directives in AngularJS - Part 1

A directive can be defined as a way to give instructions to process something. In programming terms a directive can be defined using special tags such as <%! %>, <%-- --%>,<%? %> etc. In AngularJS we can define our custom directives by using the “directive” function. Let’s understand why there is a requirement for creating a directive? In AngularJS a directive is basically a template which contains some “html” elements forming a particular design to be displayed on the webpage when the directive is used on the page. Main advantage of creating directives allows us to create reusable components which can be used on multiple views or within a same view but on multiple locations.

Benefits of using custom directive:

  1. A reusable component.
  2. Using directive will make our html cleaner.
  3. Will allow other developer to easily understand your code.
  4. Our html page will be HTML5 compliant.

Enough of theory let’s get into practical.

Let’s suppose I’ve a database which contains an “Employee” table with fields “EmpId”,”Name” and “Address”. The following is the table structure with some data.

Employee

And we want to display these records on to the page but with a specific design format. The following snapshot gives an idea about the same:

display these records

As you can see from the above snapshot we are displaying data with a specific design format by displaying employee name and his address one below the other inside a box. In ASP.NET MVC4 we can achieve such types of data display design format by using .cshtml Inline code (e.g. by implementing @foreach loop over the no. of records existing in the Employee table and writing html code inside the loop). In AngularJS we can achieve these things by implementing directives.

Let’s start with coding. I’m using MVC4 application built using VS2012 and SQL Server 2008 R2, bootstrap Css, jQuery and AngularJS 4.0.

Step 1: To load bootstrap and AngularJS.

For this, I’ve added the CDN path reference of these files in _Layout.cshtml file.

  1. <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" />  
  2. <script type="text/javascript" src="~/Scripts/jquery-1.8.2.js"></script>  
  3. <script src="https://code.AngularJS.org/1.4.0/angular.min.js" type="text/javascript"></script>  
Step 2: Define AngularJS Controller and Directive.

For this I’ve added a new script file with the name “Home.js” inside the “~/Script/app” folder. The following is the code snippet of the “Home.js”.
  1. var myApp = angular.module("myApp", []);  
  2.   
  3. myApp.controller("homeController", ["$scope""$http", function($scope, $http) {  
  4.     $http.get("http://localhost:55571/Home/GetEmployeeDetails")  
  5.         .success(function(data) {  
  6.             $scope.employees = data;  
  7.         })  
  8.         .error(function(data, status) {  
  9.   
  10.         });  
  11. }]);  
  12.   
  13. myApp.directive("employeeDetails", function() {  
  14.     return {  
  15.         template: '<a href="#" class="list-group-item" ng-repeat="emp in employees"><h4 class="list-group-item-heading">{{emp.Name}}</h4><h6 class="list-group-item-text">{{emp.Address}}</h6></a>'  
  16.     }  
  17. });  
Note:
  1. The one highlighted in gray color is the AngularJS controller in which angular framework would be injecting $scope object (which acts a glue between the view and the model) and $http (for getting data from external resources by sending ajax request).

  2. The one highlighted in yellow color is the directive where we are defining our template with mix of html elements and angular directive like ng-repeat and with interpolation “{{ }}”.

  3. In this case my custom directive name is “employeeDetails”.

    1. I’ve used pascal casing for naming my directive.

    2. If you have a look at famous frameworks like bootstrap it makes use of the conventions like “list-group-item-heading” or “list-group-item” for naming its css classes. It makes use of “-“ (hypen) sign wherever space is required.

    3. But in JavaScript we can’t define a variable with bootstrap conventions because – (hypen) in JavaScript means – (Minus). So if we use hyphen during variable declaration it will give us an error message.

    4. As per HTML 5 standards we can define our custom attribute with the data-attribute name. AngularJS is adhering to HTML5 standards that’s why every AngularJS built in directive name starts with ng- directive name such as ng-repeat, ng-app, ng-controller etc.

    5. In order to maintain the HTML 5 standards, AngularJS provided us with a facility of defining our custom attribute with pascal casing and it will automatically render the directive tag as HTML 5 compliant. So wherever you need a directive with the name “<employee-details>” you would simply create a directive with name “employeeDetails” inside your AngularJS script and it will be rendered as <employee-details>.

      1. If you want a rendered directive with name <customer-name> then inside your angular script you’ll create a directive with the name “customerName”.

      2. If you want a rendered directive with name <customer-query-result> then inside your angular script you’ll create a directive with name “customerQueryResult”.

Step 3: Referencing the Home.js file to the Index.cshtml file.

  1. @{  
  2.     ViewBag.Title = "Index";  
  3.     Layout = "~/Views/Shared/_Layout.cshtml";  
  4. }  
  5.   
  6.   
  7. <script src="~/Scripts/app/Home.js"></script>  
  8. <div class="container">  
  9.     <div class="row">  
  10.         <div class="col-md-12">  
  11.             <h1>Dashboard</h1>  
  12.             <div ng-app="myApp" style="width:400px;">  
  13.                 <div ng-controller="homeController">  
  14.                     <h3>Employee List</h3>  
  15.                     <div class="list-group">  
  16.                         <employee-details></employee-details>  
  17.                     </div>  
  18.                 </div>  
  19.             </div>  
  20.         </div>  
  21.     </div>  
  22. </div>  
Point to note:
  1. Over here we’ve just injected the directive name which was in our case “employeeDetails”.

  2. But while injecting we’d used <employee-details> instead of “employeeDetails” and when AngularJS finds this tag / directive it will automatically place the directive template which we created by following a normalized process.

Step 4: Executing the application.

Executing the application
This is the output you get. Now press F12 to check how AngularJS injected our custom directive.

output

If you carefully look at the code snippet which AngularJS created then we can say that it has enclosed our directive inside the “<employee-details>” tag. But what if we want to get rid ofthe <employee-details> tag. Then in this case we just need to add the replace: true property value in the custom directive below the template section. The following is the code snippet for the same.

  1. myApp.directive("employeeDetails", function() {  
  2.     return {  
  3.         template: '<a href="#" class="list-group-item" ng-repeat="emp in employees"><h4 class="list-group-item-heading">{{emp.Name}}</h4><h6 class="list-group-item-text">{{emp.Address}}</h6></a>',  
  4.         replace: true  
  5.     }  
  6. });  
Now let’s try to run the application.

run the application

And you’ll find that the <employee-details> tag completely replace with our directive.

Now let’s move on to explore more on AngularJS directive. With the custom directive which we created, we can have full control on how to render this directive i.e. whether we want to render it as an html attribute or html element. For doing this we just need to make use of restrict property which accepts the value as “Attribute”, “Element”, “AttributeElement”, ”Class” Or “Comment”.
  1. For restricting the directive to be used only as attribute: In this case we need to set the restrict property value to either “A” or “Attribute”. This will allow us to use the directive as an attribute of any HTML element.
    1. myApp.directive("employeeDetails", function() {  
    2.     return {  
    3.         restrict: "A",  
    4.         template: '<a href="#" class="list-group-item" ng-repeat="emp in employees"><h4 class="list-group-item-heading">{{emp.Name}}</h4><h6 class="list-group-item-text">{{emp.Address}}</h6></a>',  
    5.         replace: false  
    6.     }  
    7. });  
    And in our html we can use this directive as an attribute of any HTML element. For e.g. look at the following code.
    1. <div ng-controller="homeController">  
    2.     <h3>Employee List</h3>  
    3.     <div class="list-group">  
    4.         <p employee-details></p>  
    5.     </div>  
    6. </div>  
    Over here I’m using employee-details as an attribute of <p> tag. Save the page and run the application and see how AngularJS rendered our webpage.

  2. For restricting the directive to be used only as an Element: This one we’ve already seen. In this case we need to set the restrict property value to either “E” or “Element”. This will allow us to use the directive as an element only.
    1. myApp.directive("employeeDetails", function() {  
    2.     return {  
    3.         restrict: "E",  
    4.         template: '<a href="#" class="list-group-item" ng-repeat="emp in employees"><h4 class="list-group-item-heading">{{emp.Name}}</h4><h6 class="list-group-item-text">{{emp.Address}}</h6></a>',  
    5.         replace: false  
    6.     }  
    7. });  
    And in our html we can use this directive as an attribute of any HTML element. For e.g. look at the following code.
    1. <div ng-controller="homeController">  
    2.     <h3>Employee List</h3>  
    3.     <div class="list-group">  
    4.         <employee-details></employee-details>  
    5.     </div>  
    6. </div>  
    Over here I’m using employee-details as an element. Save the page and run the application and see how AngularJS rendered our webpage.

    Note: If we restrict property value to “A” and in our html we’d used it as an element then in this case it won’t be rendering any record when you run the page. The same applies if you restrict the property value to “E” and in html use the directive as an attribute of any html element. No records would be displayed like the following snapshot.

    snapshot

  3. For restricting the directive to be used as an Attribute or Element: This is the default setting for the restrict property. It allows us to use the directive as an attribute as well as an element. For explicitly setting the value we just need to set it “AE” or “AttributeElement”.

  4. For restricting the directive to be used as an element class: This feature is not by default turned on. It basically allows us to use our directive as a CSS class i.e. wherever we’ve defined an html element with the class name same as the directive name AngularJS will replace that portion with our custom directive template. You need to explicitly turn it on by restricting the value to “C” or “Class”.
    1. myApp.directive("employeeDetails", function() {  
    2.     return {  
    3.         restrict: "C",  
    4.         template: '<a href="#" class="list-group-item" ng-repeat="emp in employees"><h4 class="list-group-item-heading">{{emp.Name}}</h4><h6 class="list-group-item-text">{{emp.Address}}</h6></a>',  
    5.         replace: false  
    6.     }  
    7. });  
    And in our html we can use this directive as a css class of any HTML element. For e.g. look at the following code.
    1. <div ng-controller="homeController">  
    2.     <h3>Employee List</h3>  
    3.     <div class="list-group">  
    4.         <div class="employee-details"></div>  
    5.     </div>  
    6. </div>  
    Employee List

  5. For restricting the directive to be used as an html comment: This features is also by default not turned on you need to explicitly turn it on. It’ll render our custom angular directive where we’d made use of comment with the directive name. For restricting the value to comment we just need to specify the value as “M” or “Comment”. Also the replace option must be set to true in this case.
    1. myApp.directive("employeeDetails", function() {  
    2.     return {  
    3.         restrict: "M",  
    4.         template: '<a href="#" class="list-group-item" ng-repeat="emp in employees"><h4 class="list-group-item-heading">{{emp.Name}}</h4><h6 class="list-group-item-text">{{emp.Address}}</h6></a>',  
    5.         replace: true  
    6.     }  
    7. });  
    And in our html we need to define the comment like defined in the following code.
    1. <div ng-controller="homeController">  
    2.     <h3>Employee List</h3>  
    3.     <div class="list-group">  
    4.         <!-- directive: employee-details -->  
    5.     </div>  
    6. </div>  

We can also define our directive template inside an external html page and reference that page within the AngularJS directive declaration. This would be useful if you want to have complex template with more lines of html code to be defined as an AngularJS directive. Declaring this complex template with the way we used above may be error prone. So what we can do is we can define an html page and reference that page inside our AngularJS custom directive.

To demonstrate this I’ve created a new folder named “Directive” within my MVC application. And I’ve added a new html page with the name employeeDetails.html.

employeeDetails.html Code:

  1. <a href="#" class="list-group-item" ng-repeat="emp in employees">  
  2.     <h4 class="list-group-item-heading">{{emp.Name}}</h4>  
  3.     <h6 class="list-group-item-text">{{emp.Address}}</h6>  
  4. </a>  
And update the directive declaration with the following lines of code.
  1. myApp.directive("employeeDetails", function() {  
  2.     return {  
  3.         restrict: "M",  
  4.         templateUrl: '../Directive/employeeDetails.html',  
  5.         replace: true  
  6.     }  
  7. });  
Now run the application and see the same output.