Understanding Scopes in AngularJS Custom Directives

Dhananjay Kumar / Thursday, June 11, 2015
In this post, we will learn about different kinds of scopes in AngularJS custom directives. To begin, we’ll start with a high-level introduction of directives and then focus on scopes.
Directives
Directives are one of the most important components of AngularJS 1.X and have the following purposes:

1.       To give special meaning to the existing element

2.       To create a new element

3.       To manipulate the DOM

Beyond ng-app, ng-controller, and ng-repeat, there are plenty of built-in directives that come with AngularJS, including:

·         ng-maxlength

·         ng-minlength

·         ng-pattern

·         ng-required

·         ng-submit

·         ng-blur

·         ng-change

·         ng-checked

·         ng-click

·         ng-mouse

·         ng-bind

·         ng-href

·         ng-init

·         ng-model

·         ng-src

·         ng-style

·         ng-app

·         ng-controller

·         ng-disabled

·         ng-cloak

·         ng-hide

·         ng-if

·         ng-repeat

·         ng-show

·         ng-switch

·         ng-view

 
Mainly, directives perform either of the following tasks:

·         Manipulate DOM

·         Iterate through data

·         Handle events

·         Modify CSS

·         Validate data

·         Perform Data Binding

Even though there are many built-in directives provided by the Angular team, there are times when you might need to create your own custom directives. A custom directive can be created either as an element, attribute, comment, or class. In this post, a very simple custom directive can be created as shown in the listing below:
 
MyApp.directive('helloWorld', function () {
    return {
        template: "Hello IG"
    };
});
 
While creating custom directives, it’s important to remember:
• The directive name must be in the camel case;
• On the view, the directive can be used by separating the camel case name by using a dash, colon, underscore, or a combination of these.
 
Scopes in Custom Directives
Scopes enter the scene when we pass data to custom directives. There are three types of scopes, with a couple illustrated respectively with the image:

1.       Shared scope

2.       Inherited scope

3.       Isolated scope

 
We can create a custom directive with an inherited scope by setting the scope property to true, as shown in the listing below:
MyApp.directive('studentDirective', function () {
    return {
        template: "
{{student.name}} is {{student.age}} years old !!
",
        replace: true,
        restrict: 'E',
        scope : true ,
        controller: function ($scope) {
            console.log($scope);
        }
    }
});
 
Shared and Inherited Scope
Shared scope and inherited scope are relatively easier to understand. In a shared scope, directives share the scope with the enclosed controller.
Let us assume that we have a controller, as shown in the listing below:
 
MyApp.controller('StudentController', ['$scope', function ($scope) {
    console.log($scope);
    $scope.student = {
        name: "dj",
        age: 32,
        subject: [

            "math",

            "geography"

        ]

    }
 
    $scope.setGrade = function (student) {
        student.grade = "A+"
    }
 
}]);
 
Next, let’s go ahead and create a custom directive:
 
MyApp.directive('studentDirective', function () {
    return {
        template: "
{{student.name}} is {{student.age}} years old !!
",
        replace: true,
        restrict: 'E',
        controller: function ($scope) {
            console.log($scope);
        }
    }
});
 
Here, we can use the studentdirective on the view:
 
<div ng-controller="StudentController">
            <student-directive> student-directive>
  div>
In the above snippet, we are using the directive inside the div, where the ng-controller directive is set to StudentController. Since we have not set any value for the scope property in the directive, by default that works in the shared scope mode. Directives can access the properties attached to the controller scope. Any changes to the properties in the directive would be reflected in the controller and vice versa. You’ll also notice that I’m printing the scope id for both the controller and the directive, and both ids must be the same.
 
There is one problem with the shared scope: we cannot pass data explicitly to the directive; the directive directly takes the data from the enclosed controller.
In the inherited scope, the directive inherits the scope of the controller. Let’s take a look at how to create a directive with inherited scope below:
 
MyApp.directive('studentDirective', function () {
    return {
        template: "
{{student.name}} is {{student.age}} years old !!
",
        replace: true,
        restrict: 'E',
        scope : true ,
        controller: function ($scope) {
            console.log($scope);
        }
    }
});
 
An inherited scope is very useful in nested custom directives.
 
Isolated Scope
In Isolated scope, the directive does not share a scope with the controller; both directive and controller have their own scope. Data, however, can be passed to the directive scope in three possible ways.

1.       Data can be passed as a string using the @ string literal

2.       Data can be passed as an object using the = string literal

3.       Data can be passed as a function the & string literal

 
 
An isolated scope is very important because it allows us to pass different data to the controller. To understand it better, let’s assume that we have a controller as listed below:
 
MyApp.controller("ProductController", function ($scope) {
    $scope.product1 = {
        name: 'Phone',
        price: '100',
        stock: true
    };
    $scope.product2 = {
        name: 'TV',
        price: '1000',
        stock: false
    };
    $scope.product3 = {
        name: 'Laptop',
        price: '800',
        stock: false
    };
 
    $scope.ShowData = function () {
        alert("Display Data");
    }
 
});
 
As you can see here, we have three different products, and we want to pass it differently.
 
Pass data as a string
In the isolated scope, we can pass data as a string using the @ string literal. We can create a custom directive which will accept the string as an input parameter, as demonstrated in the listing below:
 
MyApp.directive('inventoryProduct', function () {
    return {
        restrict: 'E',
        scope: {
            name: '@',
            price:'@'
        },
        template: '
{{name}} costs {{price}} $
Change name
'
    };
});
 
In the above listing, we are passing two string parameters using the @ literal, meaning a string will be passed in the name and price variable.  The directive can be used on the view where, in the example, you’ll see we are passing a string value for name and the price in the directive.
 
<div ng-controller="ProductController">
    <h1>{{product1.name}}h1>
    <inventory-product name="{{product1.name}}" price="{{product1.price}}">inventory-product>
div>
 
On running the application, we should able to see the name and the price of product1.
 
When we click on the Change name button, only the name of the directive will be changed and ProductController product1 object would not be affected due to the isolated scope.
Also keep in mind that when we pass data in isolated scope as a string, it gets passed in a one-way manner, so any change in the controller scope will be reflected in the directive. However, a change in the directive would not be reflected in the controller.
 
Pass data as an object
In isolated scope, we can pass data as an object using the = string literal. We can create a custom directive which will accept an object as the input parameter as shown in the listing below:
 
MyApp.directive('inventoryProduct', function () {
    return {
        restrict: 'E',
        scope: {
            data: '='
        },
        template: '
{{data.name}} costs {{data.price}} $
Change name
'
    };
});
 
In the above listing, we are passing one object parameter using the = literal. Here, the object will be passed to the data variable. The directive can be used on the view as shown in the listing below. As we see, we are passing an object value for a data variable in the directive.
 
 
<div ng-controller="ProductController">
    <h1>{{product1.name}}h1>
    <inventory-product data="product1">inventory-product>
    <h1>{{product2.name}}h1>
    <inventory-product data="product2">inventory-product>
    <h1>{{product3.name}}h1>
    <inventory-product data="product3">inventory-product>
div>
 
Per the listing, we are using the directives three times and passing three distinct objects as input. On running, we’ll see the output as shown below:
 
 
Passing the object in the isolated scope works in two-way binding mode, meaning any changes in the directive would be reflected in the enclosed controller. Let us say we pass product1 twice as shown in the listing below:
 
<div ng-controller="ProductController">
            <inventory-product data="product1">inventory-product>
            <inventory-product data="product1">inventory-product>
        div>
 
When we run the application, the same object will be passed to both instances of the directive.
 
Clicking any of the Change name buttons will change the name of both the instance of the directives because the same object is passed, and passing an object works in two-way mode. Any change in the directive would be reflected in the enclosing controller and vice versa.
Calling an external function
We can call an external function in the enclosed directive using the literal variable &. As noted with the ProductController, there is a ShowData() function. This function can be called in a custom directive by modifying the directive as shown below:
 
MyApp.directive('inventoryProduct', function () {
    return {
        restrict: 'E',
        scope: {
            data: '&',         
        },
        template: '

{{data.name}} costs {{data.price}} $

Change name
'
    };
});
 
Here the directive can be used on the view as shown here:
 
<div ng-controller="ProductController">
            <inventory-product data="ShowData()">inventory-product>
 div>
 
Conclusion
In this post, we started with a basic understanding of directives and then moved to scopes, where we learned about:

1.       Shared scope: directive and controllers share the scope and the data. We cannot pass data explicitly to the directive.

2.       Inherited scope: directive inherits the scope of the controller. We cannot pass data explicitly to the directive.

3.       Isolated scope: directive and controllers don’t share the data and scope. We can pass data either as a string or an object explicitly to the directive.

I hope you find this post helpful, and after working on your own with directives, take a look at our advanced set of HTML5 & JavaScript UI controls and components, Ignite UI. You can download IgniteUI now and see what it can do for you! Thanks for reading!