Directives in AngularJS Simplified With Examples: Part 1

Thanks First

At the very beginning of this article, I want to give credit to the following two online courses. What I learned from these courses is covered in this article.

What a Directive is

In angularJS directives do the following:

  1. It attaches the specified behavior to DOM elements.
  2. It creates a new element in the DOM.
  3. It transforms the DOM elements.

In simple words, using directives, we can modify the behavior of a specific DOM element or add a new custom element to the DOM. For example ng-show is a directive. It modifies the behavior of an element to specify whether that element will be visible or not.

Let us consider the following code listing as an example:

  1. <div ng-show="true">  
  2.     Hi I will be visible  
  3. </div>  
  4. <div ng-show="false">  
  5.     Hi I will not be visible  
  6. </div>  
We are using the built-in directive ng-show to amend the behavior of the div. First the div will be visible whereas the second div won't.

First custom directive

A very simple custom directive can be created as shown in the following listing:
  1. myApp.directive('myFirstDirective', function () {  
  2.     return {  
  3.         template: "<b>Hello from custom directive</b>",  
  4.         restrict: "E"  
  5.     }  
  6. })  
The preceding is the minimal directive we can create. A custom directive can be created using the directive method. It takes a directive name and the function as the input parameters. There are some important points you must keep in mind when creating a directive.
  1. A directive name is specified in camel case.
  2. The view directive can be used by separating the camel case name of the directive, either using a dash, colon, or underscore.
  3. A combination of dash, underscore or colon can also be used.

We have given the directive name as myFirstDirective. On the view it can be used either as my-first-directive or my:first:directive or my_first_directive or even as my_first-directive or my-first:directive.

On the view custom directive myFirstDirective can be used as shown in the following listing. As you see we are using the custom directive with various combinations of dash, underscore and colon. For angular all three options are exactly the same. 

  1. <body>  
  2.     <my-first-directive></my-first-directive>  
  3.     <br />  
  4.     <my:first:directive></my:first:directive>  
  5.     <br />  
  6.     <my_first_directive></my_first_directive>  
  7.     <br />  
  8.     <my:first_directive></my:first_directive>  
  9.     <br />  
  10.     <my-first-directive />  
  11. </body>  

In the Chrome browser the developer tools on selecting the inspect element option, you can see that the directive template has been replaced with the HTML.



Custom directive can be used on the view in one of the following four ways.

  1. As an attribute: set restrict value to A
  2. As a custom element: set restrict value to E
  3. As a comment: set restrict value to M
  4. As a class: set restrict value to C

Out of these four methods, as an attribute and as an element are widely used. In older version of some browsers the comment and class ways of using a directive can be used.

Custom Directive with data binding

Let us proceed to create a custom directive that will use the angularjs bindings. We have the following controller:

  1. myApp.controller('studentscontroller', ['$scope', function ($scope) {  
  2.     $scope.student = {  
  3.         name: "dj",  
  4.         age: 32,  
  5.         subject: [  
  6.             "math",  
  7.             "geography"  
  8.         ]  
  9.     }  
  10. }]);  
Usually to display data from the controller, we use a binding expression on the view. Another way to display data is using the custom directive that will use the data binding. We can create a custom directive with the binding expression as shown in the listing below:
  1. myApp.directive('studentDetail', function () {  
  2.     return {  
  3.         template: "<b>hey {{student.name}} is {{student.age}} old </b>",  
  4.         restrict: "E"  
  5.     }  
  6. });  
As you notice inside the custom directive studentDetail, we are using the binding expression. Now we can use the studentDetail custom directive on the view as shown in the following listing:
  1. <div ng-controller="studentscontroller">  
  2.     <student-detail></student-detail>  
  3. </div>  
On the view you will get data renderd using the custom directive as shown in the image below:


And on the element inspection in the Chrome browser developer tools, you will find an element in the DOM as shown in the image below.



In the DOM if we want to replace the custom element then that can be done by setting the replace option in the custom directive. Let us modify the studentDetail directive by setting the replace attribute to true.

  1. myApp.directive('studentDetail', function () {  
  2.     return {  
  3.         template: "<b>hey {{student.name}} is {{student.age}} old </b>",  
  4.         restrict: "E",  
  5.         replace: true  
  6.     }  
  7. });  
Now in the DOM, we will see that the custom element has been removed as shown in the image below.


Using templateUrl

When we create a complex custom directive, a template as a string could be tough to handle. We can have a template in an external HTML file that can be loaded using the templateUrl attribute. We can move the template to an external HTML file that must have the same name as the directive. So let us add a studentDetail.html file to the application and move the template string to the HTML file.

studentDetail.html

  1. <div>  
  2.     <b>hey {{student.name}} is {{student.age}} old </b>  
  3. </div>  
We need to modify the directive as shown in the listing below:
  1. myApp.directive('studentDetail', function () {  
  2.     return {  
  3.         templateUrl: "studentDetaildj.html",  
  4.         restrict: "E",  
  5.         replace: true  
  6.     }  
  7. });  
Let us be a bit creative and use bootstrap to make the template more immersive. I am using the bootstrap panel. Using the ng-show directive, we are hiding/showing the subject div.
  1. <div class="panel panel-primary">  
  2.     <div class="panel-heading">  
  3.         {{student.name}} {{student.age}}  
  4.     </div>  
  5.     <div ng-show='!!student.subject'>  
  6.         Subjects:  
  7.         <ul ng-repeat="s in student.subject">  
  8.             <li>{{s}}</li>  
  9.         </ul>  
  10.     </div>  
  11. </div>  
Events in the directive

Let us see how to work with events in directives. Let us say that we have an event in the controller as shown in the following listing.
  1. myApp.controller('studentscontroller', ['$scope', function ($scope) {  
  2.     $scope.student = {  
  3.         name: "dj",  
  4.         age: 32,  
  5.         subject: [  
  6.             "math",  
  7.             "geography"  
  8.         ]  
  9.     }  
  10.   
  11.     $scope.setGrade = function (student) {  
  12.         student.grade = "A+"  
  13.     }  
  14.   
  15. }]);  
We have added the setGrade function to the controller. We can use this event directly in the directive template as listed below.
  1. <div class="panel panel-primary">  
  2.     <div class="panel-heading">  
  3.         {{student.name}} {{student.age}}  
  4.     </div>  
  5.     <div ng-show='!!student.subject'>  
  6.         Subjects:  
  7.         <ul ng-repeat="s in student.subject">  
  8.             <li>{{s}}</li>  
  9.         </ul>  
  10.     </div>  
  11.     <div ng-show='!!student.grade'>  
  12.         {{student.grade}}  
  13.     </div>  
  14.     <div ng-show='!student.grade'>  
  15.         <button class="btn btn-success" ng-click="setGrade(student)">set grade</button>  
  16.     </div>  
  17. </div>  
In the directive template we added a button and using the ng-click directive we are handling the event on the button. The browser application will look like as shown in the next image.



The problem with the preceding approach is that the click event resides in the parent controller rather than being a part of the directive. Hence it violates the rule of encapsulation. The idea of the scenario is we will have a click event function as the part of the directive.

The AngularJS directive supports a controller inside that. We can add a controller inside the directive as shown in the listing below.

  1. myApp.directive('studentDetail', function () {  
  2.     return {  
  3.         templateUrl: "studentDetail.html",  
  4.         restrict: "E",  
  5.         replace: true,  
  6.         controller: function ($scope) {  
  7.             $scope.setGrade = function (student) {  
  8.                 student.grade = "B+"  
  9.             }  
  10.         }  
  11.   
  12.     }  
  13. });  
The browser application will render exactly the same as it was running when the setGrade function was part of the controller.

Scopes in directives

AngularJS directives are of the following three types:
  1. Shared scope
  2. Inherited scope
  3. Isolated scope

Shared scope

Default scope is the shared scope. So far we have been working with the shared scope. In the shared scope, the directive shares the scope of the controller, in which it is enclosed in.

Let us again consider the example used previously. The studentDetail directive is enclosed inside the studentsController. By default the studentDetail directive has the same scope as the studentsController.

To understand it better try logging the value of the $scope to the console for both the controller and the directive as shown in the listing below.

  1. myApp.controller('studentscontroller', ['$scope', function ($scope) {  
  2.     $scope.student = {  
  3.         name: "dj",  
  4.         age: 32,  
  5.         subject: [  
  6.             "math",  
  7.             "geography"  
  8.         ]  
  9.     }  
  10.     console.log($scope);  
  11. }  
  12. ]);  
  13.   
  14. myApp.directive('studentDetail', function () {  
  15.     return {  
  16.         templateUrl: "studentDetail.html",  
  17.         restrict: "E",  
  18.         replace: true,  
  19.         controller: function ($scope) {  
  20.             $scope.setGrade = function (student) {  
  21.                 student.grade = "B+"  
  22.             }  
  23.             console.log($scope);  
  24.         }  
  25.   
  26.     }  
  27. });  
In the Google Chrome developer tool you will find that the id of both of the $scopes are the same.



In the shared scope the directive shared the scope of the controller it is enclosed in. Shared scope can be depicted by the next image. As you see the $scope object is being shared between the controller and the directive. If in the directive we change the data in the $scope object, it would be reflected to the controller.

In shared scope, the data attached to the $scope can be changed in the directive and it would be visible in the controller.

Inherited scope

Another option is a directive that can inherit the scope of the controller it is enclosed in. In this case the scope of the controller will be visible to the directive whereas the directive scope won't be visible to the controller. The controller scope becomes the parent of the directive scope. The inherited scope can be set by setting the value of the scope property to true as shown in the next code listing.

  1. myApp.controller('studentscontroller', ['$scope', function ($scope) {  
  2.     $scope.student = {  
  3.         name: "dj",  
  4.         age: 32,  
  5.         subject: [  
  6.             "math",  
  7.             "geography"  
  8.         ]  
  9.     }  
  10.     console.log($scope);  
  11. }  
  12. ]);  
  13.   
  14. myApp.directive('studentDetail', function () {  
  15.     return {  
  16.         templateUrl: "studentDetail.html",  
  17.         restrict: "E",  
  18.         replace: true,  
  19.         scope: true,  
  20.         controller: function ($scope) {  
  21.             $scope.setGrade = function (student) {  
  22.                 student.grade = "B+"  
  23.             }  
  24.             console.log($scope);  
  25.         }  
  26.   
  27.     }  
  28. });  
In the Google Chrome developer tool we will find that the id of the directive $scope and the controller $scope are different.



However if we examine the parent of the directive child scope, we will find that the parent is set to the id of the controller scope.



If we change data in the directive scope then that would not be reflected to the controller scope.

Isolated scope

Isolated scope is the most used scope in the angularJS custom directive. It allows us to work with various data from the different instances of the same custom directive. Let us consider the studentDetail directive and use it multiple times as shown in the following listing:

  1. <div class="container" ng-controller="studentscontroller">  
  2.     <student-detail>  </student-detail>  
  3.     <student-detail>  </student-detail>  
  4.     <student-detail>  </student-detail>  
  5. </div>  
The browser application will be rendered as shown in the next image. No point to guess that all four entry of directives are using the same data from the controller.


When you click on one of the set grades, it will set the grade for all the four students as shown in the next image.



For sure we may not want this kind of behavior in the application. This problem can be solved with the isolated scope. To create a directive with the isolated scope, set the scope property to an object when creating the directive. It is shown in the listing below.

  1. myApp.directive('studentDetail', function () {  
  2.     return {  
  3.         templateUrl: "studentDetail.html",  
  4.         restrict: "E",  
  5.         replace: true,  
  6.         scope: {},  
  7.         controller: function ($scope) {  
  8.             $scope.setGrade = function (student) {  
  9.                 student.grade = "B+"  
  10.             }  
  11.             console.log($scope);  
  12.         }  
  13.   
  14.     }  
  15. });  
We have created an isolated scope for the directive. Now the scope is not shared in the directive and the controller. At this point, on running the application we will find that the data is not displayed in the application because the scope is not shared.

When you click on one of the set grades, it will set the grade for all the four students as shown in the next image.


Using a diagram let us try to understand shared and isolated scope.

data shared



Now the question in front of us is, in isolated scope how to share the data from the controller to the directive? In Isolated scope data can be shared using the local scope property. A directive with the isolated scope can talk to the outside world using the local scope properties. There are the following three options in local scope properties.



@local scope property

The @local scope property can access the string from the outside the directive. Using the @local scope property a string value can be passed to the directive.

Let us assume we have a simple directive as shown in the listing below.

  1. myApp.directive('aStudentDirective', function () {  
  2.     return {  
  3.         scope: {  
  4.             name: '@'  
  5.         },  
  6.         template: "Hi <b>{{name}}</b>",  
  7.         restrict: "E"  
  8.     }  
  9. })  
In the preceding directive we are using the @ local scope property to read the string from outside the directive. A directive is used as shown in the listing below.
  1. <div class="container" ng-controller="studentscontroller">  
  2.     <a-student-directive name="{{student.name}}"></a-student-directive>  
  3. </div>  
In the directive student.name from the studentscontroller can be used. We are passing data to the directive as the string.

If you want a local scope property name in the directive to be different than the property name then that can be done as shown in the listing below.

  1. myApp.directive('aStudentDirective', function () {  
  2.     return {  
  3.         scope: {  
  4.             name: '@studentname'  
  5.         },  
  6.         template: "Hi <b>{{name}}</b>",  
  7.         restrict: "E"  
  8.     }  
  9. })  
A directive can be used as shown in the listing below:
  1. <div class="container" ng-controller="studentscontroller">  
  2.     <a-student-directive studentname="{{student.name}}"></a-student-directive>  
  3. </div>  
If we change the value of a student name in the controller, the directive will be updated. However any change in the directive local property will not make any change in the controller.

=local scope property

The character = local scope property is used to pass an object to the directive from the outside world. It also supports two-way binding. To understand it let us proceed to recreate the controller as shown in the listing below.
  1. myApp.controller('studentscontroller', ['$scope', function ($scope) {  
  2.     $scope.student1 = {  
  3.         name: "dj",  
  4.         age: 32,  
  5.         guardian: {  
  6.             mother: "abc",  
  7.             father: "xyz"  
  8.         },  
  9.         subject: [  
  10.             "math",  
  11.             "geography"  
  12.         ]  
  13.     }  
  14.     $scope.student2 = {  
  15.         name: "foo",  
  16.         age: 14,  
  17.         guardian: {  
  18.             mother: "pqw",  
  19.             father: "rty"  
  20.         },  
  21.         subject: [  
  22.             "physics",  
  23.             "geography"  
  24.         ]  
  25.     }  
  26.     $scope.student3 = {  
  27.         name: "loo",  
  28.         age: 21,  
  29.         guardian: {  
  30.             mother: "mnq",  
  31.             father: "wsy"  
  32.         },  
  33.         subject: [  
  34.             "math",  
  35.             "bilogy"  
  36.         ]  
  37.     }  
  38. }  
  39. ]);  
We have added more students to the $scope. This is a pretty straight forward controller. We will use this controller to understand the local scope property =.

Next let us create a directive as in the following:
  • Using the isolated scope
  • Using the = local scope property to pass object to the directive from the outside world

A directive can be created as shown in the listing below.

  1. myApp.directive('studentDetail', function () {  
  2.     return {  
  3.         templateUrl: "studentDetail.html",  
  4.         restrict: "E",  
  5.         replace: true,  
  6.         scope: {  
  7.             student: '='  
  8.         },  
  9.         controller: function ($scope) {  
  10.             $scope.setGrade = function (student) {  
  11.                 student.grade = "B+"  
  12.             }  
  13.         }  
  14.   
  15.     }  
  16. });  
In the HTML we can use the directive passing a different value for the student as shown in the listing below.
  1. <div class="container" ng-controller="studentscontroller">  
  2.     <student-detail student="student1"></student-detail>  
  3.     <student-detail student="student2"></student-detail>  
  4.     <student-detail student="student3"></student-detail>  
  5. </div>  
I have also modified the template in studentDetail.html as shown in the following listing.
  1. <div class="panel panel-primary">  
  2.     <div class="panel-heading">  
  3.         <h2>{{student.name}} {{student.age}}</h2>  
  4.     </div>  
  5.     <div class="panel-body">  
  6.     <div ng-show='!!student.subject'>  
  7.         Subjects:  
  8.         <ul ng-repeat="s in student.subject">  
  9.             <li>{{s}}</li>  
  10.         </ul>  
  11.     </div>  
  12.     <div class="well">  
  13.         Guardians:<br />  
  14.         Father : {{student.guardian.father}}<br />  
  15.         Mother : {{student.guardian.mother}}<br />  
  16.     </div>  
  17.     <div ng-show='!!student.grade'>  
  18.         {{student.grade}}  
  19.     </div>  
  20.     <div ng-show='!student.grade'>  
  21.         <button class="btn btn-success" ng-click="setGrade(student)">set grade</button>  
  22.     </div>  
  23. </div>  
  24. </div>  
On running the application in the browser, we will find that the data of three different students have been rendered. Now when you set the grade of one student, another student will not be affected.



Inherited scope example

Now to understand the inherited scope better, let us say we have a requirement to collapse and expand each student panel when the user clicks on the panel header. To do this:

  1. Add a ng-click directive to the panel header. When the user clicks on the header the function will be called in the directive controller.
  2. Add a ng-hide directive to the panel body. On the clicking of the panel header the panel body will be either collapsed or expanded.

Let us start by adding the ng-click and the ng-hide directive.

  1. <div class="panel panel-primary">  
  2.     <div class="panel-heading" ng-click="hide()">  
  3.   
  4.         <h2>{{student.name}} {{student.age}}</h2>  
  5.     </div>  
  6.     <div class="panel-body" ng-hide="isHidden">  
  7.         <div ng-show='!!student.subject'>  
  8.             Subjects:  
  9.             <ul ng-repeat="s in student.subject">  
  10.                 <li>{{s}}</li>  
  11.             </ul>  
  12.         </div>  
  13.         <div class="well">  
  14.             Guardians:<br />  
  15.             Father : {{student.guardian.father}}<br />  
  16.             Mother : {{student.guardian.mother}}<br />  
  17.         </div>  
  18.         <div ng-show='!!student.grade'>  
  19.             {{student.grade}}  
  20.         </div>  
  21.         <div ng-show='!student.grade'>  
  22.             <button class="btn btn-success" ng-click="setGrade(student)">set grade</button>  
  23.         </div>  
  24.     </div>  
  25. </div>  
In the preceding code listing, we have added a ng-click directive (see the panel-header div). On the click event hide() function in the directive a controller will be called. We also added a ng-hide directive (see the panel-body div)

Next in the directive we need to add a hide() function and isHidden data to the $scope object of the controller that is the part of the directive.

  1. myApp.directive('studentDetail', function () {  
  2.     return {  
  3.         templateUrl: "studentDetail.html",  
  4.         restrict: "E",  
  5.         replace: true,  
  6.         scope: {  
  7.             student: '='  
  8.         },  
  9.         controller: function ($scope) {  
  10.             $scope.isHidden = false;  
  11.             $scope.setGrade = function (student) {  
  12.                 student.grade = "B+"  
  13.             }  
  14.             $scope.hide = function () {  
  15.                 $scope.isHidden = !$scope.isHidden;  
  16.             }  
  17.         }  
  18.   
  19.     }  
  20. });  
On running the application in the browser, we will find that the data of three separate students have been rendered and we can expand and collapse the student panel as shown in the image below:



Parent and child directives

We notice that studentDetail directive is becoming large. As the application grows, it will be tough to manage a directive that is too large. Let us proceed to take the guardian data and move it to a separate directive called studentGuardian.

We have created a template as shown in the listing below.

studentGuardian.html

  1. <div class="well">  
  2.     Guardians:<br />  
  3.     Father : {{student.guardian.father}}<br />  
  4.     Mother : {{student.guardian.mother}}<br />  
  5. </div>  
Let us proceed to create a directive with the shared scope as shown in the listing below:
  1. myApp.directive('studentGuardian', function () {  
  2.     return {  
  3.         templateUrl: "studentGuardian.html",  
  4.         restrict: "E"  
  5.     }  
  6. })  
In the studentDetail directive, we can use the studentGuardian directive as shown in the listing below:
  1. <div class="panel panel-primary">  
  2.     <div class="panel-heading" ng-click="hide()">  
  3.   
  4.         <h2>{{student.name}} {{student.age}}</h2>  
  5.     </div>  
  6.     <div class="panel-body" ng-hide="isHidden">  
  7.         <div ng-show='!!student.subject'>  
  8.             Subjects:  
  9.             <ul ng-repeat="s in student.subject">  
  10.                 <li>{{s}}</li>  
  11.             </ul>  
  12.         </div>  
  13.         <student_guardian></student_guardian>  
  14.         <div ng-show='!!student.grade'>  
  15.             {{student.grade}}  
  16.         </div>  
  17.         <div ng-show='!student.grade'>  
  18.             <button class="btn btn-success" ng-click="setGrade(student)">set grade</button>  
  19.         </div>  
  20.     </div>  
  21. </div>  
Now we have a nested directive studentGuardian inside the main directive studentDetail. And the studentGuardain directive works with the shared scope. We got a similar kind of requirement to expand and collapse the guardian section.

Let us start by adding ng-click and ng-show to the studentGuardian directive template. Now we have two divs on the studentGuardian directive template. One will be displayed in expanded mode and the other in collapsed mode.

  1. <div class="well" ng-show='isHidden' ng-click="showGuardian()">  
  2.     Guardians::::::  
  3. </div>  
  4. <div class="well" ng-show='!isHidden' ng-click="hideGuardian()">  
  5.     Guardians:<br />  
  6.     Father : {{student.guardian.father}}<br />  
  7.     Mother : {{student.guardian.mother}}<br />  
  8. </div>  
Next is the studentGuardian directive as in the following:
  • Add a controller
  • Add isHidden data to the $scope of the studentGauradian template. We have isHidden data in the $scope of the studentDetail directive also. On purpose we are giving the same name to see the behavior of the inherited scope
  • Added two functions hideGuardain and showGuardain to set the value of isHidden to false and true respectively
  • The Value of the scope property is set to true.

The modified directive is listed next.

  1. myApp.directive('studentGuardian', function () {  
  2.     return {  
  3.         templateUrl: "studentGuardian.html",  
  4.         restrict: "E",  
  5.         scope: true,  
  6.         controller: function ($scope) {  
  7.             $scope.isHidden = false;  
  8.             $scope.hideGuardian = function () {  
  9.                 $scope.isHidden = true;  
  10.                 console.log('h' + $scope.isHidden);  
  11.             };  
  12.             $scope.showGuardian = function () {  
  13.                 $scope.isHidden = false;  
  14.                 console.log('s ' + $scope.isHidden);  
  15.             }  
  16.   
  17.         }  
  18.     }  
  19. });  
On running the application in the browser, we will find that now the guardian can be expanded and collapsed separately. If we change the value of scope to false then when we click in guardian the entire panel will be collapsed/expanded since the isHidden variable is shared in the shared scope.

Summary

In this article we learned about directives, custom directives and scopes in anularJS. In the next article we will focus on decorator directives, the link function and so on. I hope you find this article useful. Thanks for reading.

Up Next
    Ebook Download
    View all
    Learn
    View all