Skip to main content
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Brian Ghidinelli
Ben Nadel at dev.Objective() 2015 (Bloomington, MN) with: Brian Ghidinelli

Setting Prototype Properties Using Inherited Scope Methods In AngularJS

By
Published in Comments (14)

In AngularJS, every Controller gets its own $scope instance. This $scope instance binds the model - defined by the Controller - to the View - which renders the model using HTML annotations. Every instance of $scope has, as its prototype, the $scope of its parent context in the DOM (Document Object Model) tree. This is true all the way up the HTML document, until you get to the root Controller, which uses the $rootScope as its prototype. This means that every $scope inherits the properties of the $scope above it. This is perfect when you want to "Read" a $scope property; however, due to the nature of the prototype chain, setting a property on an inherited scope can be a bit tricky.

Prototypal inheritance is pretty badass - as long as you understand how it works. And, since AngularJS' $scope system is deeply invested in the use of the prototype chain, that understanding is critical in an AngularJS application. When it comes to reading and writing properties of an object, you have to understand that access to those properties is asymmetric.

When you read a property from an object (that inherits from a prototype chain), the JavaScript runtime will continue to move up the prototype chain until it finds a prototype object with the given property. If it finds it, JavaScript returns the value; if it gets to the root prototype and is unable to find the given property, JavaScript returns "undefined".

When you set a property on an object in the prototype chain, the JavaScript runtime will write that property directly to the given object. JavaScript doesn't care if the property has already been defined higher up in the prototype chain, a "write" command will only work on the current object reference.

NOTE: The delete() commands also work solely on the current object reference; you cannot delete a prototype property using a reference to the sub-class instance.

In an AngularJS application, if you want to set the property of a parent $scope from within a child $scope, you have to be able to work within the constraints of this asymmetric property access. And, in order to do that, you can define "setter" methods in the prototype chain that provide lexical access to a given object in the prototype chain.

To see this in action, here is a demo in which the window title "model" is defined by the root Controller and then subsequently set by a descendant controller:

<!doctype html>
<html ng-app="Demo" ng-controller="AppController">
<head>
	<meta charset="utf-8" />

	<!--
		When setting the window title, we can display a default value
		in the tags, then take it "over" using the ngBind directive.
		This binds the tag content to the given Model (scope value).
	-->
	<title ng-bind="windowTitle">AngularJS Demo Loading</title>
</head>
<body>

	<h1>
		Setting Prototype Properites Using Scope Methods In AngularJS
	</h1>

	<form ng-controller="FormController" ng-submit="save()">

		<p>
			Window Title:<br />
			<input type="text" ng-model="name" size="30" />
			<input type="submit" value="Update Title" />

			<!-- Compare to property-only setting. -->
			<a ng-click="setProperty()">Set Property Directly</a>
		</p>

	</form>


	<!-- Load AngularJS from the CDN. -->
	<script
		type="text/javascript"
		src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js">
	</script>
	<script type="text/javascript">


		// Create an application module for our demo.
		var Demo = angular.module( "Demo", [] );


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// Define our root-level controller for the application.
		Demo.controller(
			"AppController",
			function( $scope ) {

				// Set up the default programmtic window title. Once
				// the app runs, this will overwrite the value that
				// is currently set in the HTML.
				$scope.windowTitle = "Default Set In AppController";

				// This App Controller is the only controller that
				// has access to the Title element. As such, we need
				// to provide a way for deeply nested Controllers to
				// update the window title according to the page
				// state.
				$scope.setWindowTitle = function( title ) {

					// This function closure has lexical access to
					// the $scope instance associated with this App
					// Controller. That means that when this method
					// is invoked on a "sub-classed" $scope instance,
					// it will affect this scope higher up in the
					// prototype chain.
					$scope.windowTitle = title;

				};

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// Define our Form controller.
		Demo.controller(
			"FormController",
			function( $scope ) {

				// Default the empty form field.
				$scope.name = "Go Ahead - Try Me";

				// When the form is saved, let's update the window
				// title with the new value.
				$scope.save = function() {

					// The setWindowTitle() method is inherited from
					// the scope prototype chain - in this case, it is
					// being inherited from the AppController $scope.
					this.setWindowTitle( this.name );

				};

				// When the user clicks the set-property link, we're
				// going to try to set the window title using just a
				// direct proprty reference.
				$scope.setProperty = function() {

					// Set the window title directly.
					this.windowTitle = this.name;

				};

			}
		);


		// -------------------------------------------------- //
		// -------------------------------------------------- //

	</script>

</body>
</html>

In the AppController, you can see that we have defined a $scope method, setWindowTitle(). This method definition creates a closure that is lexically-bound to the AppController constructor function. This means that the setWindowTitle() function body retains a reference to the $scope variable defined within AppController context.

The $scope variable passed into the FormController constructor function uses the AppController's $scope as its prototype. This means that the FormController $scope inherits the windowTitle property as well as the setWindowTitle() method. The FormController can then use this inherited method to set the windowTitle property of the parent scope.

As I said before, property access on the prototype chain is asymmetric. As such, if we go to set the inherited windowTitle property directly (as with the setProperty() demo method), we will only affect the current $scope, not the parent $scope (which is the one bound to the Title element). This is why the latter method fails to update the window title (see video).

AngularJS exposes the $scope prototype chain through the $scope's $parent property. $parent points to the $scope instance that is being used as the prototype for the current scope instance. While this might seem like a good property to use, you will quickly find that it is quite brittle. The $parent property tightly couples to the current Controller to the DOM (Document Object Model) tree such that changes to Controller's context will quickly break references to the $parent scope.

That, and the fact that AngularJS directives (like ngRepeat) create their own $scope, has lead me to believe that the use of $parent is a poor "code smell." Instead, I would advocate the exposing of setter methods as a way to write values to ancestors within the prototype chain.

Want to use code from this post? Check out the license.

Reader Comments

3 Comments

Hi,

first of all, I love your blog, loads of great stuff about angularjs ;)

Now, I had a question about this inheritance, it's unclear to me what inherits the scope prototype, is it the

$scope</scope> object or the controller's function?
From what you say, I understand that it's the <code>$scope

but from the code, in the second controller, you use

this

instead of

$scope

to access the parent's scope methods.

Cheers!

3 Comments

ok, me again (sorry for the wrong formatting in the previous post). I see that in your next post, you have a mix of

$scope

and

this

to access the inherited methods, so I guess they are equivalent? Is there a logic to use one against the other?

3 Comments

Ok, it's almost starting to be spam ;)

Obviously I wasn't well awake when I read your posts.

this

within the function does refer to

$scope

as this is where the function is called. They are thus the same.

However, wouldn't it be clearer to always use

$scope

(

this

is always twisted in javascript :D)

1 Comments

Thank you for this tutorial, it really helped me a lot! I've spent more than hour, trying to understand, how to pass a variable to nested controller in ng-repeat statement!

1 Comments

Hi Ben, thanks for the article.

I actually like (smells okay) using $parent for ng-repeat and ng-switch, when you know the immediate parent has the model you want to use. Using something like $parent.$parent smells bad to me.

"asymmetric" - I think it would be good to expand your article to distinguish between primitives and objects. With primitives, property access on the prototype chain is asymmetric. With objects it is not... i.e., in a child scope, I can write to a parent scope object property the same way I read it. E.g., suppose I have this parent scope object property:

$scope.someParentObj = { name: 'Mark' }

In a child scope that prototypically inherits from this parent scope, this works (no setter required):

&lt;a ng-click="someParentObj.name='Ben'">switch to Ben&lt;/a>

Here's a SO post that goes into a little more detail: http://stackoverflow.com/questions/12405005/angularjs-bind-scope-of-a-switch-case

15,848 Comments

@Mortimer,

Sorry for the slow response - work has been killing me lately :) Glad you're enjoying the posts, though!

The behavior of "this" in JavaScript is one of the most irksome and confusing features. But, I think you are on the correct track. In either case, "this" and "$scope" would point to the same thing. I used "this" to try to drive home the fact that one scope inherits from the other.

15,848 Comments

@Denys,

Using ngRepeat and controllers is an interesting thing. I haven't used a controller on an ngRepeat many times; but, it can be useful to capture per-item interactions:

<ul ng-controller="ListController">
 
	<li ng-repeat="item in items" ng-controller="ListController">
		....
	</li>
 
</ul>

Since the ngRepeat executes at a higher priority than ngController, you will end up creating an instance of ListController for each instance of the LI tag. This can be useful if you need to provide behavior that is specific to one instance (such as complex mouse-enter / mouse-leave behaviors).

if there's something in particular that you're trying to do, I'd love to hear it.

15,848 Comments

@Mark,

I'll definitely use $parent from time-to-time to access the parent scope. But, there are some times where it's a bit more complicated since the $scope-based method and the invocation may be separated by too many levels. For example, if the setWindowTitle() were on the root controller... and was being invoked by some secondary-level navigation (think ngSwitch for section and another ngSwitch for 'tab" or something).

But, I'm happy you mentioned the fact that if you put an object in the scope (rather than a simple value) you can successfully mutated it from any level of the $scope chain inheritance.

In fact, in a recent video I was watching in which Misko Hevery talked about "AngularJS Best Practices":

http://youtu.be/ZhfUv0spHCY?t=29m19s

... he states that $scope should be "read only" in terms of the view. This goes to what you are saying. If you have an ngModel attached to a simple value on the $scope, changing the input will mutate the value on the $scope directly... in essence "writing" to the $scope. However, if you were to attache the name ngModel to the property of an _object_ that is in the $scope, then the ngModel will mutate the containing object, and *not* the $scope directly.

2 Comments

I'm trying to create a recursive directive (a tree view) and realize that I need to use this kind of inheritance. But it's not working. I have a home-controller wherein I've defined:

$scope.updateTree = function(searchObj){ console.log(searchObj); };

and from my directive(s) I have tried to place an instance of this in both my link function as well as a controller defined by the directive. In the browser console, I can clearly trace a path from the scope of the directive to the scope containing my function, though. I receive "Object #<Object> has no method 'updateTree'" though... it's frustrating. :(

15,848 Comments

@Dan,

It sounds like maybe your directive is using the "isolate scope"? If your directive configuration (the hash you return at the end of your directive statement) contains the "scope:{}" definition, then your directive $scope will NOT inherit from the parent scope (from my understanding). As such, that updateTree() method will not be available in the directive scope.

That said, I almost never have a separate scope on my directives, so I am not too familiar with the way they interact.

2 Comments

@Ben, thanks for replying! I wandered off this path of development and only just returned to it a few minutes ago. I learned that yes, it is related to the isolated scope that I am using. I can pass reference of the parent scope's function that I want to use as a parameter of my directive, and then define it locally in the directive's scope using the "&" value. I'm still playing around now with moving values back & forth between the scopes, and exploring further down the non-prototypical path, but it is progress.

9 Comments

How about using a service to get and set the model data. The parent controller and all nested controllers would use this service to get and set the data. If you change something in the View, the $watch expression would catch it and set the model properties accordingly.

Code:

$scope.$watch('viewModel.hideUser', function () {
userService.setHidden($scope.viewModel.hideUser);
});

I'm getting my head wrapped around Angular; please take this with a grain of salt.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel