Defining Scope Behavior Inside AngularJS Custom Directives

This article is a continuation of my previous article. If you haven’t had a look at it please go through the following link:

Creating Custom Directives In AngularJS - Part1

We’ll get into the theory a bit later. Firstly, we will focus on the architecture of the application used for demonstrating this article. I am using VS2013 ASP.NET MVC 4 application, AngularJS, Bootstrap and jQuery.

I have created ASP.NET MVC 4 application and added the necessary AngularJS and bootstrap libraries and defined a custom AngularJS directive with the name “contactInfo.html”.

contactInfo.html
  1. <div class="list-group">  
  2.     <a href="#" class="list-group-item" ng-repeat="person in Persons">  
  3.         <h3 class="list-group-item-heading">  
  4.             {{person.Name}}  
  5.         </h3>  
  6.         <h5 class="list-group-item-text">  
  7.             {{person.Address}}  
  8.         </h5>  
  9.         <h6 class="list-group-item-text">  
  10.             {{person.ContactNo}}  
  11.         </h6>  
  12.     </a>  
  13. </div>
I have updated my _Layout.cshtml file to include bootstrap and AngularJS links.

_Layout.cshtml
  1. <!DOCTYPE html>  
  2. <html ng-app="myApp">  
  3. <head>  
  4.     <meta charset="utf-8" />  
  5.     <meta name="viewport" content="width=device-width" />  
  6.     <title>@ViewBag.Title</title>  
  7.     @Scripts.Render("~/bundles/jquery")  
  8.     <link href="~/Content/bootstrap.min.css" rel="stylesheet" />  
  9.     <script src="~/Scripts/angular.min.js"></script>  
  10. </head>  
  11. <body>  
  12.     @RenderBody()  
  13.     
  14.     @RenderSection("scripts", required: false)  
  15. </body>  
  16. </html>  
I have added a new folder inside our Script folder with the name “app” and added a new javascript file name “Home.js” to the app folder.

Index.cshtml
  1. @{  
  2.     ViewBag.Title = "Index";  
  3. }  
  4.   
  5. <script src="~/Scripts/app/Home.js"></script>  
  6. <div class="container" ng-controller="homeController">  
  7.     <div class="row">  
  8.         <div class="col-md-12">  
  9.             <div ng-app="myApp" style="width:400px;">  
  10.                 <div ng-controller="homeController">  
  11.                     <h3>Person Details</h3>  
  12.                     <div class="list-group">  
  13.                         <contact-info></contact-info>  
  14.                     </div>  
  15.                 </div>  
  16.             </div>  
  17.         </div>  
  18.     </div>  
  19. </div>    
Now we are all set with the architecture of the application and everything is in place, so let’s move towards a bit of theory.

Well there are 3 ways through which you can define your $scope object behavior within custom AngularJS directives. They are the following:

Shared Scope

From the above link demo we can conclude that $scope object inside our controller is accessible in our directives also i.e. whatever models or function we define on our controller $scope we can access all of them inside our custom AngularJS directive. This is what we call as Shared Scope. By default controller $scope object would be shared with our custom AngularJS directives. Any changes made to the scope object inside our custom AngularJS directive will have a reflection in the controller $scope object and vice versa. To explore more into this let’s look at the following demo. Here is the updated “Home.js” file.

Home.js
  1. var myApp = angular.module("myApp", []);     
  2. myApp.controller("homeController", ["$scope", function ($scope) {  
  3.     $scope.Persons = [  
  4.             { Name: "Vishal Gilbile", Address: "132 Lions Street Andheri (E)", ContactNo: '9820109742' },  
  5.             { Name: "Rahul Bandekar", Address: "135 Lions Street Andheri (E)", ContactNo: '9820109748' }  
  6.     ];  
  7. }]);  
  8.   
  9.   
  10. myApp.directive("contactInfo", function () {  
  11.     return {  
  12.         restrict: "AE",  
  13.         templateUrl: '../../Directives/contactInfo.html',  
  14.         replace: false  
  15.  }  
  16. });  
Let’s run the application and here is the output.



Figure 1: Person Detail

Now let’s update the $scope model inside our custom AngularJS controller to prove that the scope object is shared between the two (i.e. between controller and custom directive).

Here's the updated custom angular directive code snippet.
  1. myApp.directive("contactInfo", function () 
  2. {  
  3.     return {  
  4.         restrict: "AE",  
  5.         templateUrl: '../../Directives/contactInfo.html',  
  6.         replace: false,  
  7.         controller: function ($scope) {  
  8.             $scope.Persons.push({ Name: "Shrikant Maharana", Address: "1324 Lions Street Andheri (W)", ContactNo: "9820104874" });  
  9.         }  
  10.     }  
  11. });  
  1. We’ve updated our Custom AngularJS directive and also we’ve provided it with its own controller having $scope object injected.
  2. We tried updating the $scope.Person model by adding a new person data to it inside our custom AngularJS directive.

Let’s try to run the application and here is the output.



Figure 2: Detail

So we are now getting 3 records (2 from controller and 1 from our AngularJS custom directive). So we proved our point that the $scope object was shared between the controller and the custom directive.

Inherited Scope

Within this our $scope object will be inherited from controller $scope object. Custom directive $scope object will still be able to access/update the model of $scope object of the controller. But if we create some model data inside our custom directive $scope object this model will only be accessible within the directives and not to the controller $scope object. For implementing this $scope behavior we just need to add the scope property with value true to our custom directive. For demonstrating this we’ve added a new model to the custom directive $scope object named “count” which displays the count of records from the model.

contactInfo.html

  1. <div class="list-group">  
  2.         <a href="#" class="list-group-item" ng-repeat="person in Persons">  
  3.         <h3 class="list-group-item-heading">  
  4.             {{person.Name}}  
  5.         </h3>  
  6.         <h5 class="list-group-item-text">  
  7.             {{person.Address}}  
  8.         </h5>  
  9.         <h6 class="list-group-item-text">  
  10.             {{person.ContactNo}}  
  11.         </h6>  
  12.      </a>  
  13.      <h6><strong>{{count}} Records Found.</strong></h6>  
  14. </div> 
Home.js
  1. myApp.directive("contactInfo", function () 
  2. {  
  3.     return {  
  4.         restrict: "AE",  
  5.         templateUrl: '../../Directives/contactInfo.html',  
  6.         replace: false,  
  7.         scope: true,  
  8.         controller: function ($scope) {  
  9.             $scope.count = 0;  
  10.             $scope.Persons.push({ Name: "Shrikant Maharana", Address: "1324 Lions Street Andheri (W)", ContactNo: "9820104874" });  
  11.             $scope.count = $scope.Persons.length;  
  12.         }  
  13.     }  
  14. });  
Let’s run the application.



Figure 3: Run the Application

Now let’s try to access the “count” model inside our controller $scope. For demonstrating this I’ve updated some piece of code. Here is the updated code snippet.

Index.cshtml
  1. @{  
  2.     ViewBag.Title = "Index";  
  3. }  
  4. <script src="~/Scripts/app/Home.js"></script>  
  5. <div class="container" ng-controller="homeController">  
  6.     <div class="row">  
  7.         <div class="col-md-12">  
  8.             <div ng-app="myApp" style="width:400px;">  
  9.                 <div ng-controller="homeController">  
  10.                     <h3>Person Details</h3>  
  11.                     <contact-info></contact-info>  
  12.                     <label>Controller Count Value: {{count}}</label>  
  13.                 </div>  
  14.             </div>  
  15.         </div>  
  16.     </div>  
  17. </div>    
Home.js
  1. var myApp = angular.module("myApp", []);  
  2.   
  3.   
  4. myApp.controller("homeController", ["$scope", function ($scope) {  
  5.     $scope.Persons = [  
  6.             { Name: "Vishal Gilbile", Address: "132 Lions Street Andheri (E)", ContactNo: '9820109742' },  
  7.             { Name: "Rahul Bandekar", Address: "135 Lions Street Andheri (E)", ContactNo: '9820109748' }  
  8.     ];  
  9.     $scope.count = 0;  
  10. }]);  
  11.   
  12.   
  13. myApp.directive("contactInfo", function () {  
  14.     return {  
  15.         restrict: "AE",  
  16.         templateUrl: '../../Directives/contactInfo.html',  
  17.         replace: false,  
  18.         scope: true,  
  19.         controller: function ($scope) {  
  20.             $scope.count = 0;  
  21.             $scope.Persons.push({ Name: "Shrikant Maharana", Address: "1324 Lions Street Andheri (W)", ContactNo: "9820104874" });  
  22.             $scope.count = $scope.Persons.length;  
  23.         }  
  24.     }  
  25. });  
  26. });
NOTE
  1. Inside our Home controller we’ve defined a model named “count” with initial value set to zero.
  2. Inside our Index.cshtml file we made a provision for displaying the “controller count model value” inside a label control.

Now let’s try to run the application.



Figure 4: Detail 2

From the above snapshot we can see that the controller count model value is still zero while the custom directive count model value is 3 which is the length of the person array. So this states that the count model defined inside our custom directive is only accessible to the $scope object of our custom AngularJs directive and not to the controller $scope object.

Also to prove this more we can log both the $scope object and see whether the count model is different for the controller $scope object and for directive $scope object.



Figure 5:
Scope

The above snapshot is self-explanatory.

Isolated Scope

In this both the $scope object (i.e. from Controller and from the Custom directive) will have their own isolated $scope object. Isolation means both the $scope object won’t be able to share/see any data of each other. For creating isolated scope we just need to set the $scope property of the custom directive to {}. Here's the updated code snippet for the same.

Home.js

  1. myApp.directive("contactInfo", function () 
  2. {  
  3.     return {  
  4.         restrict: "AE",  
  5.         templateUrl: '../../Directives/contactInfo.html',  
  6.         replace: false,  
  7.         scope: {},  
  8.         controller: function ($scope) {  
  9.             $scope.count = 0;  
  10.             $scope.Persons.push({ Name: "Shrikant Maharana", Address: "1324 Lions Street Andheri (W)", ContactNo: "9820104874" });  
  11.             $scope.count = $scope.Persons.length;  
  12.             console.info("Directive Scope:");  
  13.             console.log($scope);  
  14.         }  
  15.     }  
  16. });
Let’s run the application and this time you’ll see the following output.



Figure 6: Output

Now we are getting an error message in the browser console stating that “Cannot read property push of undefined”. This is because now our controller $scope Person model is no longer accessible to the $scope object of the custom directive because of $scope isolation. But well there are ways through which you can inject a hole for this isolation so that you can access the model data of controller $scope inside the custom directive.

For more information about how to do that you can have a look at my article.

Creating Custom Directives In AngularJS - Part 2.