AngularJS Directive — Scopes

Scopes in AngularJS Directives

I intend to talk about AngularJS Directives and different types of scopes that can be used. There are 3 main ways in which scope can be passed to the directive from the invoking view (HTML):

  • Default Scope
  • Inherited Scope
  • Isolated Scope

And the isolated scope could itself be:

  • Oneway
  • Twoway
  • Method Reference

Default Scope

In this type, the scope from the outer view just flows into the directive. To put it another way, the scope is passed as “reference” to the directive. All the scope data will be immediately available within directive to consume. Likewise, any change made to the scope within directive will be reflected in the invoking view immediately.

view and directive bound vis scope

This is achieved by specifying the scope as

scope: false // false is the default value

in the directive definition.

In the example below, ‘name’ is flown into directive and any change made is reflected outside immediately. Because the scope is set true, directive gets the scope as its caller. Now any changes made to the name in the directive will be immediately reflected in the ‘MyController’ controller.

var myApp = angular.module('myApp', []);

myApp.controller('MyController', function($scope) {
$scope.name = "Jack";
});
// usage: <div upper-case/></div>
myApp.directive('upperCase', function() {
return {
scope: false,
template: 'Name: <input ng-model="name"></input>'
}
});

Live fiddle is @ https://jsfiddle.net/madhanganesh/830rn1fm/

Inherited Scope

In this type a new scope is inherited from view scope. In Javascript, objects are inherited using prototypical inheritance. This scenarios is depicted in diagram below:

This is achieved by specifying the scope as

scope: true

in the directive definition.

In the example below you can try changing the name in the directive. This change won’t be reflected outside due to ‘inherited’ scope. The value only flows inside but any change will result in a copy for the directive and won’t be reflected outside.

In the live example below, you can see the ‘name’ is flown into directive; but change made in the directive will not be reflected outside.

In the example below, the directive scope is given a value of true. This means a scope is created for directive which “inherits” from the scope of controller. As per the prototypical inheritance rules of javascript, the derived object (directive’s scope) refers to the same properties of parent object (controller’s scope). Any changes made to the controller will be reflected in the directive, until, a change is made to the “name” property in the directive. At that time a new object is created for directive and changes are opaque from each other.

var myApp = angular.module('myApp', []);

myApp.controller('MyController', function($scope) {
$scope.name = "Jack";
});
// usage: <div upper-case/></div>
myApp.directive('upperCase', function() {
return {
scope: true,
template: 'Name: <input ng-model="name"></input>'
}
});

Live fiddle is @ https://jsfiddle.net/madhanganesh/57sb6yg8/

Isolated Scope

In this type of scope definition the directive gets an independent scope which does not receive or send back any data to containing view.

This is achieved by specifying the scope as

scope: {}

in the directive definition.

In the example below isolated scope is achieved by passing an empty object {} to the directive.

var myApp = angular.module('myApp', []);

myApp.controller('MyController', function($scope) {
$scope.name = "Jack";
});
// usage: <div upper-case/></div>
myApp.directive('upperCase', function() {
return {
scope: {},
template: 'Name: <input ng-model="name"></input>'
}
});

Live fiddle is @ https://jsfiddle.net/madhanganesh/7d37w08e/

Isolated scope is really more to this than completely restricting any data to be passed in (and out) of directive. At one end of the spectrum you can completely restrict the data as shown in above example. But it is possible to pass one or more attributes to directive and capture them as a scope for the directive. The flexibility comes in the way the passed attribute is interpreted inside the directive. The attribute passed to directive could be interpreted in one of the 3 ways:

  • as an oneway that gets evaluated before passing to directive
  • as a twoway a reference to property in calling scope
  • as a reference to method in calling scope

Expression

The caller of the directive can explicitly pass a value as shown below:

<div my-directive name='{{name}}' />
// an expression is passed to the directive

The way it is captured inside the directive is:

scope: {
name: '@name'
}

@ symbol tells angular to consider the name attribute as an expression and evaluate it before assigning the value to text property. Because the expression is evaluated, it is a one-way binding and any update made on the value inside directive will not be reflected outside.

In the example below, see that the name property is passed to the directive. This is an expression that is evaluated and assigned to directive’s isolated scope. This means value “Jack” is evaluated and assigned as text inside the directive.

var myApp = angular.module('myApp', []);

myApp.controller('MyController', function($scope) {
$scope.name = 'Jack';
});

// usage: <div my-directive name='{{name}}' />
myApp.directive('upperCase', function() {
return {
scope: {
name: '@name'
},
template: 'Name: <input ng-model="name"></input>'
}
});

Live fiddle is @ https://jsfiddle.net/madhanganesh/ybzcryt0/4/

Because name is a text in the directive the changes are local to directive and nothing is shared with the parent controller.

Two-way (reference to property)

The caller of the directive can pass a reference to a property to the directive, as in:

<div my-directive name='data.name' />
// a property reference to the directive

The way it is captured inside the directive is:

scope: {
name: '=name'
}

= symbol tells angular to consider the name attribute as a reference to specific property of calling scope. So the value is passed in to directive and any change made on the attribute is reflected outside immediately.

In the example below, see that the name property is passed to the directive. This is a reference to name property in parent scope.

var myApp = angular.module('myApp', []);

myApp.controller('MyController', function($scope) {
$scope.name = 'Jack';
});

myApp.directive('upperCase', function() {
return {
scope: {
name: '=name'
},
template: 'Name: <input ng-model="name"></input>'
}
});

Live fiddle is @ https://jsfiddle.net/madhanganesh/o74nvcj3/1/

Because name is a reference to the property in controller any changes on either side will be reflected to both.

Reference to method

The way it is passed from calling view is:

<div my-directive nameFn="getName()" />
// a method reference is passed to the directive

The way it is captured inside the directive is:

scope: {
nameFn: '& nameFn'
}

& symbol tells angular to consider the nameFn attribute as a reference to specific method of calling scope. This method can then be executed from within the directive when required.

In the example below, you can see the nameFn property is a method whose reference is passed inside the directive. This function is executed from the directive but evaluated in the context of calling scope.

var myApp = angular.module('myApp', []);

myApp.controller('MyController', function($scope) {
$scope.name = 'Jack';
$scope.getName = function() {
return $scope.name;
}
});

myApp.directive('upperCase', function() {
return {
scope: {
nameCb: '&name'
},
//template: 'Name: <input ng-model="nameFn()"></input>'
template: 'Name: {{textCb()}}'
}
});

Live fiddle is @ https://jsfiddle.net/madhanganesh/6w4msr5k/1/

Summary

As you can see directive scope is quite exhaustive, below summary captures the possibilities and their meaning.

  • Default : Directive gets the same scope as its containing view
  • Inherited: Directive gets a scope that is inherited from the scope of containing view
  • Isolated: Directive gets a scope that independent of containing view

And within Isolated scope there are 3 possibilities:

Oneway : The directive attribute is considered as an expression and evaluated before assigning to specific property of directive’s isolated scope

Twoway: The directive attribute is considered as a reference to a property of the containing scope

Method Reference: The directive attribute is considered as a reference to a method of the containing scope