Creating Custom Directives In AngularJS - Part 2

This article is part 2 of the series of articles in continuation with my previous article on creating custom directives in AngularJS”.

If you haven’t had a look at it please go through the previous article:

Creating Custom Directives in AngularJS - Part 1

We’ve seen in the previous article that we made an $http request inside an AngularJS controller and stored the data retrieved as response to a variable (model) inside the controller $scope object. In our case we had defined a model variable with the name “employees” inside the $scope object where the $http response was stored. And inside our custom directive i.e. “employeeDetails” we had used this variable to iterate over the no. of records with the help of ng-repeat directive without any errors or warnings. This is because by default AngularJS allows the directives to access the properties of the parent scope. This simply means we can access $scope object details inside our custom directives, that is because the directive is a part of the controller in which it is used, so being a child it is able to access its parent resources without any errors or warnings.

Custom directive been a reusable component would be used across multiple pages and by multiple developers simultaneously and binding the custom directive data directly from the controller might result in an unexpected result. So in order to get rid off this, we need to isolate $scope object inside the directives. For doing this we need to add a property inside the custom directive declaration with the name “scope”. Here's the updated code snippet for the same.
  1. myApp.directive("employeeDetails", function()  
  2. {  
  3.     return   
  4.     {  
  5.         restrict: "E",  
  6.         templateUrl: '../Directive/employeeDetails.html',  
  7.         replace: true,  
  8.         scope: {}  
  9.     }  
  10. }); 
I’ve set the scope object to nothing in the custom directive, now just try to run the application and you’ll find that no data is displayed on the UI even though the $http request is succeeded and the $scope.employees model is been bounded with the $http response. That means we’ve successfully isolated the $scope object inside the custom directives.


Figure 1: Dashboard

Now how we will pass data to the custom directives scope object for data binding purpose. Well for this AngularJS provides us three ways for passing data from the outside environment to the AngularJS custom directives. The following table describes the same.
 

Symbol Name

Refers To

@ @ refers to text i.e. we simply can pass any textual content to the custom directives scope model.
= = refers to object i.e. we can pass object to the custom directives scope model.
& & refers to function i.e. we can pass function to the custom directives scope model which on execution will fetch some data and bind to it.

Let’s demonstrate this one by one

@ Means

It simply refers to text. It acts as a placeholder for displaying textual content in the directives elements using interpolation. Let’s look at an example, for demonstrating this, I’m creating new Controller (with name DefaultController) and Index.cshtml and Default.js. In Default.js file instead of retrieving data from the database we simply create some static model data which we would render on the screen. I’m creating a new directive with the name “defaultResult.html” which has the same format that we created in our “employeeDetails” directive. Here's the code snippet for the same:

Index.cshtml

  1. @{  
  2. ViewBag.Title = "Index";  
  3. Layout = "~/Views/Shared/_Layout.cshtml";  
  4. }  
  5. <script src="~/Scripts/app/Default.js"></script>  
  6. <div class="container">  
  7.     <div class="row">  
  8.         <div class="col-md-12">  
  9.             <h1>Dashboard</h1>  
  10.             <div ng-app="myApp" style="width:400px;">  
  11.                 <div ng-controller="defaultController">  
  12.                     <h3>Employee List</h3>  
  13.                     <div class="list-group">  
  14.                         <default-result emp-name="{{employee.Name}}" emp-address="{{employee.Address}}"></default-result>  
  15.                     </div>  
  16.                 </div>  
  17.             </div>  
  18.         </div>  
  19.     </div>  
  20. </div>
NOTE
  1. Here we have used <default-result> directive but along with that I’ve defined some custom attribute with the name empName and empAddress. These custom attributes are way of penetrating a hole for retrieving data from parent controller scope.
  2. For setting values for these attribute I’m assigning parent controller scope model properties i.e. in this case employee.Name and employee.Address to the attributes we defined.
  3. Remember the normalization process done by AngularJS (discussed in the previous article). Here we are following the same process; we defined custom attributes with name “emp-name” that means inside our javascript code we will be having a variable / property with a name  “empName” same applied to “emp-address”.

Default.js

  1. var myApp = angular.module("myApp", []);  
  2.   
  3. myApp.controller("defaultController", ["$scope", function($scope)   
  4. {  
  5.     $scope.employee =   
  6.     {  
  7.         Name: 'Vishal Gilbile',  
  8.         Address: '141/B Lions Street, Garden Lane Andheri (E)'  
  9.     }  
  10. }]);  
  11.   
  12. myApp.directive("defaultResult", function()   
  13. {  
  14.     return   
  15.     {  
  16.         restrict: "E",  
  17.         templateUrl: '../Directive/defaultResult.html',  
  18.         replace: true,  
  19.         scope:   
  20.         {  
  21.             empName: "@",  
  22.             empAddress: "@"  
  23.         }  
  24.     }  
  25. });
NOTE
  1. Here in the controller we’ve defined employee model with properties named “Name” and “Address”.
  2. Inside our isolated scope variable defined in employeeDetails directive, we’ve defined scope level variables with name “empName” and “empAddress” and it is assigned to “@” (i.e. at runtime it will hold textual content from the parent controller scope model).
  3. If you have a look at our custom attributes (emp-name & emp-address) defined in Index.cshtml, the isolated scope properties (empName & empAddress) name are same. Well it’s not a compulsion to keep it same. If you want to give some different name you can do that but with a small change. Let’s suppose you defined custom attribute name as “employee-name” and “employee-address” then in order to refer it in your isolated scope object we need to update ours.

    In index.cshtml file replace your default-result tag with the following code snippet:
  1. <default-result employee-name="{{employee.Name}}" employee-  
  2. address="{{employee.Address}}"></default-result>  
  3. In Default.js replace the isolated scope object with below code snippet.  
  4. scope:   
  5. {  
  6.     empName: "@employeeName",  
  7.     empAddress: "@employeeAddress"  
  8. }  
Looking at the above code snippet we can say that the custom attribute names are used with @ symbol to let AngularJS know from where to read the textual values for the scope properties.
  1. defaultResult.html (Our New Directive)  
  2.   
  3. <a href="#" class="list-group-item">  
  4.     <h4 class="list-group-item-heading">{{empName}}</h4>  
  5.     <h6 class="list-group-item-text">{{empAddress}}</h6>  
  6. </a>
NOTE

Here we are simply displaying empName and empAddress properties using interpolation. These properties are basically the isolated scope properties defined inside our custom directive in default.js file.

Just run the application and you’ll see the following output.


Figure 2: Employee List

= means

It means assigning an object. For demonstrating this we will switch back to our HomeController that we used in the previous article example in which we were retrieving data from the database and storing the data into the $scope.employees model object. We are using homeController, Index.cshtml file, employeeDetails.html (our Custom Directive) & Home.js file.

Index.cshtml
  1. @{  
  2. ViewBag.Title = "Index";  
  3. Layout = "~/Views/Shared/_Layout.cshtml";  
  4. }  
  5. <script src="~/Scripts/app/Home.js"></script>  
  6. <div class="container">  
  7.     <div class="row">  
  8.         <div class="col-md-12">  
  9.             <h1>Dashboard</h1>  
  10.             <div ng-app="myApp" style="width:400px;">  
  11.                 <div ng-controller="homeController">  
  12.                     <h3>Employee List</h3>  
  13.                     <div class="list-group">  
  14.                         <employee-details employee-object="employees"></employee-details>  
  15.                     </div>  
  16.                 </div>  
  17.             </div>  
  18.         </div>  
  19.     </div>  
  20. </div>
NOTE
  1. Here we’d used our <employee-details> custom directive with our custom attribute named “employee-object” in which we’d assigned the “employees” which is our controller’s $scope model object.
  2. To assign the object to the custom attribute (employee-object), here we’ve not used interpolation (i.e. {{}}) unlike what we did for assigning the property (emp.Name and emp.Address) values in the defaultController’s Index.cshtml file (i.e. in case “@” symbol).
  3. Here also the normalization process of defining variable names and attribute names remain the same. Here we defined attribute name as “employee-object” so inside our JavaScript we’ll define our property / variable with the name “employeeObject”.

Home.js

  1. var myApp = angular.module("myApp", []);  
  2.   
  3. myApp.controller("homeController", ["$scope""$http", function($scope, $http)   
  4. {  
  5.     $http.get("http://localhost:55571/Home/GetEmployeeDetails")  
  6.         .success(function(data)   
  7.         {  
  8.         $scope.employees = data;  
  9.     })  
  10.         .error(function(data, status)   
  11.         {  
  12.   
  13.     });  
  14. }]);  
  15. myApp.directive("employeeDetails", function()  
  16. {  
  17.     return {  
  18.         restrict: "E",  
  19.         templateUrl: '../Directive/employeeDetails.html',  
  20.         replace: true,  
  21.         scope: {  
  22.             employeeArrayObject: "=employeeObject" //two way binding.  
  23.         }  
  24.     }  
  25. });
NOTE

Here also the things remains same of defining property name:
  • If the property name and custom attribute name is same, then simply use “=” sign i.e. employeeObject: “=” in the isolated scope.
  • In my case I’ve used different name for custom attribute as well as for the isolated scope property name, so in this case we’d to define in the above mentioned way.

employeeDetails.html (our custom directive)

  1. <a href="#" class="list-group-item" ng-repeat="emp in employeeArrayObject">  
  2.     <h4 class="list-group-item-heading">{{emp.Name}}</h4>  
  3.     <h6 class="list-group-item-text">{{emp.Address}}</h6>  
  4. </a>
Just run the application and you’ll see the following output.



Figure 3: DashBoard2

& means

It is a function for demonstrating and we will use the same homeController and its related files (such as home.js, index.cshtml & employeeDetails.html).

Index.cshtml
  1. @{  
  2. ViewBag.Title = "Index";  
  3. Layout = "~/Views/Shared/_Layout.cshtml";  
  4. }  
  5. <script src="~/Scripts/app/Home.js"></script>  
  6. <div class="container">  
  7.     <div class="row">  
  8.         <div class="col-md-12">  
  9.             <h1>Dashboard</h1>  
  10.             <div ng-app="myApp" style="width:400px;">  
  11.                 <div ng-controller="homeController">  
  12.                     <h3>Employee List</h3>  
  13.                     <div class="list-group">  
  14.                         <employee-details employee-array-object="employees" formatted-address-fun="formatAddress(aemployee)"></employee-details>  
  15.                     </div>  
  16.                 </div>  
  17.             </div>  
  18.         </div>  
  19.     </div>  
  20. </div>
NOTE
  1. Inside our custom directive element <employee-details> we’ve added a new custom attribute with name “formatted-address-fun” and assigned a function “formatAddress(aemployee)” with a parameter to it.
  2. “formatted-address-fun” will become a property inside our javascript Home.js file with normalized name i.e. formatAddressFun.
  3. formatAddress is a function defined in the controller scope object.

Home.js

  1. var myApp = angular.module("myApp", []);  
  2.   
  3. myApp.controller("homeController", ["$scope""$http""$filter", function($scope, $http, $filter) {  
  4.     $http.get("http://localhost:55571/Home/GetEmployeeDetails")  
  5.         .success(function(data) {  
  6.         $scope.employees = data;  
  7.     })  
  8.         .error(function(data, status) {  
  9.         console.log(data);  
  10.     });  
  11.   
  12.     $scope.formatAddress = function(employee) {  
  13.         return $filter("uppercase")(employee.Address);  
  14.     }  
  15. }]);  
  16.   
  17. myApp.directive("employeeDetails", function() {  
  18.     return {  
  19.         restrict: "E",  
  20.         templateUrl: '../Directive/employeeDetails.html',  
  21.         replace: false,  
  22.         scope: {  
  23.             employeeArrayObject: "=",  
  24.             formattedAddressFun: "&"  
  25.         }  
  26.     }  
  27. });
NOTE
  1. As you can see we’ve injected one more AngularJS service named $filter in our controller function.
  2. We’ve added formatAddress as a function to the $scope object which expects employee object as a parameter and returns the employeeAddress in upper case form by using the $filter service.
  3. We’ve set replace:false here.
  4. Inside our isolated scope object I’ve defined a property with normalized name “formattedAddressFun” and assigned “&” to it.

employeeDetails.html (our Custom Directive)

  1. <a href="#" class="list-group-item" ng-repeat="emp in employeeArrayObject">  
  2.     <h4 class="list-group-item-heading">{{emp.Name}}</h4>  
  3.     <h6 class="list-group-item-text">  
  4. {{formattedAddressFun({aemployee:emp})}}  
  5. </h6>  
  6. </a>
NOTE
  1. Here we are referring to the function name defined inside the isolated scope as a property i.e. in our case “foramttedAddressFun”.
  2. For passing parameter we are using object mapping i.e. the parameter name defined in the Index.cshtml file where we have implemented this directive by adding a new custom attribute format-address-fun=”formatAddress(aemployee)”.
  3. For object mapping I simply used the same object name / parameter name i.e. aemployee and assigned the value to it. By using the following  syntax.
    {{formattedAddressFun({aemployee:emp})}}

Just try to run the application and you’ll see the following output.



Figure 4: OutPut

And our address is displayed in upper case.