Angular JS and Scope

Understanding scopes in Angular JS

Written by 42ID on October 05 2014

Sooner or later, at one point in your Angular learning curve, you are going to want to rip your hair out when dealing with scope. Fear not, its actually a very easy concept and once you have the mechanics down, it's downright intuitive.

There are two things at play here

  1. JavaScript Prototypal inheritance
  2. Child scopes created by many Angular directives. Now these child scopes in turn could either prototypically inherit from the parent scope or they could be 'isolate' scope.

Knowing the difference and how prototypal inheritance works will go a long way in clarifying many of the questions that developers have when starting out with Angular JS.

Prototypical inheritance in JavaScript

Objects in JavaScript inherit by creating a prototypical chain to the base classes. When one object inherits from another, its prototype property ( .__proto__) points to the base object. All objects inherit from Object whose prototype property is set to null. Now, when properties are accessed on an object, this object's own property list is first checked and if no matching property is found, then the object's prototype chain is traversed. The search stops when the property is found on one of the base object along the prototype chain or when it reaches the end of the inheritance chain. When inherited objects have the same property names as the base object, the base object properties are hiddenor shadowedby the inherited objects. 


var baseObj = {
	a: 2,
	b: 3,
	x: {
		x1: 'property x1 set by base object',
		x2: 'property x2 set by base object'
	}
}
var inheritedObj = Object.create(baseObj); // inherit from base object
inheritedObj.a = 4;
console.log(inheritedObj.a)
// output = 4
// baseObj.a is hidden/shadowed by inheritedObj.a
console.log(inheritedObj.b)
// output == 3

		

When properties are set on an object (with an = operator), the properties are created on the object. These properties are knows as " own properties" of an object as opposed to the properties that it inherits from the base object. In the above example, the statement:


inheritedObj.a = 4;

creates a property called 'a' on the inheritedObject and effectively hides/shadows the 'a' property on its base object.

Now, here's the tricky part. When you access a dotted property on a object like so:


inheritedObj.x.x1 = 'property x1 on baseObject set by inheritedObj';

you are now traversing the prototypal chain to the base object's 'x' property and then accessing its 'x1' property. So in this case, no **new**property is created on your object. Rather an existing property on the base object is updated. This is an important distinction that will make understanding scopes very easy.

Child Scopes in Angular JS

Let's consider the following Angular directives - ng-repeat, ng-include, ng-switch, ng-view. These directives create a child scope that is prototypically inherited from the parent scope.

So, what is the implication of the previous statement?

If you are intending to change a value of a property in the parent scope from within the child scope, you can only do so with a dotted property.

That statement is so important that we are going to repeat it again:

If you intend to change a value of a property that exists in the parent scope from within the child scope, you can only do so with a dotted property.


app.controller('MainCtrl', function($scope) {
	$scope.dataArray = [1,2,3,4,5,6,7,8,9];
	$scope.selectedItem  = -2;
	$scope.someObject = {selectedItem:-1};
});


<body ng-controller="MainCtrl">
	<p> primitive property: {{selectedItem}} dotted property: {{someObject.selectedItem}}</p>
	<div ng-repeat='i in dataArray'>
		<button ng-click="someObject.selectedItem = i;  selectedItem = i">Button {{i}}</button>
	</div>
</body>

If you run the above code you will see 2 things. Try it on plunker

  1. the value of the primitive property does not change when you click on the button
  2. value of the dotted property changes when you click on the button

Reason? When you say selectedItem = i, a new property called selectedItemis created on the child scope within the ng-repeat directive (actually a new child scope is created for every iteration of ng-repeat). This property hides/shadows the property in the parent scope. So the parent never gets updated. However, when you use someObject.selectedItem, the prototypal link is followed down to the parent scope and the parent scope is updated.

Scopes in custom Directives

By default, directives do not create a new scope, but just use the parent scope. You can have a directive prototypically inherit from its parent scope by specifying a ' scope: true' property on the directive.


app.directive('myDirective', function(){
	return{
		scope: true
	}
});

Knowing that the scope is now prototypically inherited from the parent scope, if any value needs to be updated in the parent scope from the directive, use the dotted property.

When you use directives to create reusable components and wish to protect the parent scope from being accidentally read or modified by the directive, you can then create an 'isolated' scope by specifying the ' scope: { }' property. An isolated scope is a completely new object that does not inherit from the parent scope. However, in many cases, you may still want access to some properties in the parent scope. Directives allow you establish a one-way binding or a two-way binding to properties in the parent scope by use of '=', '@' and '&' operators. In all the three cases, the parent scope property is associated with the directive scope through the use of attributes in the HTML tag representing the directive. This will be clearer with the following example.

'=' Operator:

This establishes a two-way binding between a directive scope property and its parent scope. The DOM attribute contains the name of the parent scope property to is to be bound to the local directive property. Properties bound using the = operator are immediately available in the template as well as the link function of the directive.

On the view:


<my-directive attribute-parent-property='parentProperty'>

on your directive:


app.directive('myDirective', function(){
	return{
		scope: {
			localProperty : '=attributeParentProperty'
		}, 
		template: 'Rendered by the directive: {{localProperty}}',
		link : function(scope, elem, attrs){
			var x = scope.localProperty;
		}
	}
});

'@' Operator:

This establishes an one-way binding between a parent property and directive scope property. When the value in the parent scope changes, the local variable in the directive scope also changes. Notice that you use a {{ }} in your attribute when you use the @ operator. This is because @ binds the local directive scope property to the evaluated value of the DOM attribute. Since the DOM attribute is always a string, the evaluated value is also a string.

Properties bound by the @ operator are immediately available in the template region of the directive, but not in the linking function.

On the view:


<my-directive attribute-parent-property='{{parentProperty}}'>

on your directive:


app.directive('myDirective', function(){
	return{
		scope: {
			localProperty : '@attributeParentProperty'
		},
		template: 'Rendered by the directive: {{localProperty}}'
	}
});

'&' Operator:

This establishes a binding between a directive scope property and an expression in the parent scope. The expression could be a function in the parent scope. This is useful if you want to invoke a function from the directive - such as an event handler.

On the view:


<my-directive attribute-parent-function='parentFunction()'>

on your directive:


app.directive('myDirective', function(){
	return {
		scope: {
			localFunction : '&attributeParentFunction'
		},
		template: '<button ng-click="localFunction()">Click Event invoked from the Directive</button>'
	}
});

Controller As Syntax

Angular 1.2.0 introduced the Controller As syntax. Controller As syntax hides the $scope variable and provides for a cleaner interpretation of using the controller as the view model for your view. This syntax creates a new controller object through which you can expose the properties and methods that you want the view to access. We prefer to use this style, although there's no hard and fast rule. Its just a personal preference.


app.controller('myController', function(){
	var vm = this;
	vm.message = 'Hello from controller';
});


<div ng-controller='myController as vm'>
	{{vm.message}}  <!-- writes out Hello from controller -->
</div>

Here, you don't have to explicitly inject the $scope variable into your controller. Also, since with this method, we are now using dotted properties in our views for accessing all properties, we don't run into the problem of hiding/shadowing properties we saw earlier. When working with nested controllers and hence with nested scope, the 'controller as' syntax results in a more readable code.


<div ng-controller='myController1 as vm'>
	{{vm.message}}
	<div ng-controller='myController2 as vm2'>
		{{vm2.data}}
	</div>
</div>

Was this useful?

Have questions? We have answers. Contact us, we are glad to help! 

Spread the word if you found this useful! Tweet us at @thelatestbrian and @yanamegainfo