Decorating Scope Methods In The AngularJS Prototype Chain

Posted September 24, 2012 at 9:57 AM by Ben Nadel

Tags: Javascript / DHTML

Last week, I looked at how to pass values up the $scope chain in an AngularJS application. We used a setter method in order to get around the asymmetric nature of prototype property access and mutation. This allowed us to set values into an inherited $scope instance. Now, since we needed to use a setter method; and since that setter method is inherited by the $scope prototype chain; it means that we are afforded the opportunity to decorate that setter method at various points in the $scope prototype chain.

As I discussed last week, each instance of $scope in an AngularJS application inherits, as its prototype object, the $scope that comes before it in the DOM (Document Object Model) hierarchy. This means that properties set in an ancestor scope will become available in a descendant scope. This gives each $scope instance the opportunity to create its own local version of a particular property (without corrupting ancestral $scopes). And, in the case of a $scope method, it provides a hook to decorate a method call as it gets passed up the prototype chain.

To experiment with this, we'll refactor our setWindowTitle() demo from last week. In this version, we'll decorate the setWindowTitle() at two places, adding two new features:

  • Uppercase the window title.
  • Append the phrase, "- AngularJS Demo".

In order to do this, we need to add two new $scopes (passed into two new Controllers). This requires two additional HTML elements:

  • <!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>
  • Decorating Scope Methods In AngularJS Prototype Chain
  • </h1>
  •  
  •  
  • <!--
  • I will decorate some scope values as they get "passed" up the
  • scope prototype chain.
  • -->
  • <div ng-controller="OuterController">
  •  
  • <!-- I will further decorate the title. -->
  • <div ng-controller="InnerController">
  •  
  •  
  • <form ng-controller="FormController" ng-submit="save()">
  •  
  • <p>
  • Window Title:<br />
  • <input type="text" ng-model="name" size="25" />
  • <input type="submit" value="Update Title" />
  • </p>
  •  
  • </form>
  •  
  •  
  • </div>
  •  
  • </div>
  •  
  •  
  • <!-- 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 outer controller that deocrates the title.
  • Demo.controller(
  • "OuterController",
  • function( $scope ) {
  •  
  • // Get a reference to the original setWindowTitle()
  • // method that we are about to decorate.
  • var coreSetWindowTitle = $scope.setWindowTitle;
  •  
  • // Expose a local method that will override the
  • // core setWindowTitle() method. Since each scope
  • // instance is a prototype of the scope instances
  • // below it, this version will be the one that is
  • // inherited by the scopes below it.
  • $scope.setWindowTitle = function( title ) {
  •  
  • // Decorate the title.
  • title = (title + " - AngularJS Demo");
  •  
  • // Pass the call up the prototype chain.
  • coreSetWindowTitle.call( this, title );
  •  
  • };
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Define our outer controller that deocrates the title.
  • Demo.controller(
  • "InnerController",
  • function( $scope ) {
  •  
  • // Get a reference to the "original" setWindowTitle()
  • // method that we are about to decorate. I use quotes
  • // becuase this is actually the version being
  • // inherited from the OuterController.
  • var coreSetWindowTitle = $scope.setWindowTitle;
  •  
  • // Expose the decorator to the sub-classes scopes
  • // farther down in the DOM tree.
  • $scope.setWindowTitle = function( title ) {
  •  
  • // Decorate the title.
  • title = title.toUpperCase();
  •  
  • // Pass the call up the prototype chain.
  • coreSetWindowTitle.call( this, title );
  •  
  • };
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Define our Form controller.
  • Demo.controller(
  • "FormController",
  • function( $scope ) {
  •  
  • // Default the empty form field.
  • $scope.name = "You feeling lucky, punk?";
  •  
  • // 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. This would typically
  • // be inherited from the AppController... however,
  • // this time, it is actually being inherited from
  • // the InnerController which is overwriting and
  • // decorating calls to the root setWindowTitle()
  • // scope method.
  • this.setWindowTitle( this.name );
  •  
  • };
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the OuterController and InnerController instances both decorate the setWindowTitle() method exposed by the AppController. In order to do this, they each obtain a reference to the method that they inherited; then they set, in their own local scope, a new version of the setWindowTitle() method. This new version is then inherited by their descendant scopes.

Now, when the FormController makes a call to setWindowTitle() with the phrase:

You feeling lucky, punk?

... the window ends up getting the actual title:

YOU FEELING LUCKY, PUNK? - AngularJS Demo

As you can see, the original value was decorated twice, resulting in an uppercase value with a new suffix.

Since I am just getting into AngularJS, I don't have a great use-case for this kind of $scope prototype chain manipulation. In fact, setting the window title is really the only thing that jumps to mind. That said, the architecture of the $scope chain does allow for some pretty cool stuff.




Reader Comments

Mar 7, 2013 at 7:07 PM // reply »
1 Comments

Cool article. I was pondering the usage of prototypal inheritance in angularJS - and if Angular in fact alleviates the need for it altogether


Mar 25, 2013 at 9:25 AM // reply »
11,238 Comments

@White Box,

So, there are two opportunities for inheritance in an AngularJS application: the $scope inheritance, and the model inheritance.

You don't have any [much] control over $scope inheritance. AngularJS gives that to you out of the box so that your nested views can inherit data and behavior. This is a really awesome feature.

Model inheritance (such as having one Service object inherit from another) is completely up to you. I have gone back and forth on this one. Sometimes, I like the idea; sometimes I don't. Often times, I'll remove some inheritance and replace it with a dependency-injected "helper" object that contains the shared behavior.

Furthermore, some kinds of inheritance are easier than others. Service object inheritance is relatively easy as long as you can figure out how to wire up your Factory / Service to inject your base class; then, define your prototype off that base class.

Controller-inheritance, on the other hand, is far more complex since the Controller collection doesn't really allow for factory-based building. As such, you're in a bit of Catch-22 - you can't inject a base controller UNTIL you've instantiated the sub-class controller... at which point, it's a bit too late to extend the base class.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
May 14, 2013 at 6:57 PM
The UX Of Prototyping: Low-Fidelity Is The New High-Fidelity
@Phillip, I'm not sure I follow what you mean? Are you saying that you looked at the list of widgets provided by the jQuery UI and let that be your style guide? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools