Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the New York ColdFusion User Group (May. 2008) with: Clark Valberg

Using Base Controllers In AngularJS - An Experiment

By Ben Nadel on

Yesterday, in a conversation that I was having with Chris Schetter, I was rehashing how my JavaScript style has changed somewhat since I've started using AngularJS. Specifically, I've started using the Revealing Module Pattern a lot more than I ever have before. While many things have contributed to this shift, one feature, or lack thereof, was the inability to use Base Controllers in AngularJS. Since Controllers cannot be generated within Factories, it seemed that Base Controllers could only be provided through global references (which goes against everything that AngularJS stands for). That said, after my conversation yesterday, I wanted to give Base Controllers one more try.

NOTE: This is just an experiment in using Factories to generate Controllers. I am not necessarily advocating this approach - see final paragraph.

In JavaScript, when you invoke a constructor function, the return value of the construtor matters. If you don't return anything, JavaScript will use the instantiated object as the result of the invocation. If, however, you return a value (other than "this") from the constructor function, JavaScript will use that return value as the result of the invocation.

When it comes to AngularJS, we can leverage this language feature to fake the ability to create Controllers inside of Factories. The idea is simple, but a bit confusing. And, after some trial and error, I finally got it to work. What we're going to do is turn our Controller into a factory.

Generally speaking, when you need to create a Controller in AngularJS, the following workflow happens:

AngularJS -> Controller constructor.

In this experiment, however, we're going to turn the Controller constructor into a factory that instantiates our "sub class" controller:

AngularJS -> Controller constructor (factory) -> Controller constructor.

This workflow can happen because the intermediary Controller can return a value other than itself. And, when it does this, AngularJS will ultimately use the return value as the target Controller. And, since the intermediary construtor can use dependency injection, it allows our sub-class controller to also use dependency injection.

In the following proof-of-concept, I am overriding the core "factory" convenience method so that I can use a single point of entry for both service factories and controller factories. This approach requires that I create a special naming convention for Controllers (which aligns nicely with my typical approach). Specifically, you have to give Controllers a dot-delimited namespace that ends in "Controller". For example:

friends.ListController

In the following demo, I have am defining a Base-Controller object; then, I am sub-classing it with a Controller that is used to render the AngularJS view-model.

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Using Base Controllers In AngularJS
  • </title>
  • </head>
  • <body ng-controller="demo.SubController">
  •  
  • <h1>
  • {{ title }}
  • </h1>
  •  
  • <p>
  • Foo: {{ foo }}.
  • </p>
  •  
  • <p>
  • Bar: {{ bar }}.
  • </p>
  •  
  •  
  • <!-- Load jQuery and AngularJS from the CDN. -->
  • <script
  • type="text/javascript"
  • src="//code.jquery.com/jquery-2.0.3.min.js">
  • </script>
  • <script
  • type="text/javascript"
  • src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js">
  • </script>
  • <script type="text/javascript">
  •  
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Set up the Controller-Factory. This is not a feature that
  • // AngularJS provides out of the box. As such, we have to
  • // jerry-rig our own factory using the core factory.
  • (function( core, coreFactory ) {
  •  
  • // Controllers will be defined by dot-delimited namespaces
  • // that end in "Controller" (ex. foo.BarController).
  • var pattern = /\.[^.]*?Controller$/i;
  •  
  • // As the factories are invoked, each will return the
  • // constructor for the given Controller; we can cache these
  • // so we don't have to keep re-wiring the factories.
  • var constructors = {};
  •  
  • // I proxy the core factory and route the request to either
  • // the Controller provider or the underlying factory.
  • function factory( name, controllerFactory ) {
  •  
  • // If the given injectable name is not one of our
  • // factories, then just hand it off to the core
  • // factory registration.
  • if ( ! pattern.test( name ) ) {
  •  
  • return(
  • coreFactory.apply( core, arguments )
  • );
  •  
  • }
  •  
  • // Register the Controller Factory method as a
  • // Controller. Here, we will leverage the fact that
  • // the *RETURN* value of the constructor is what is
  • // actually being used as the Controller instance.
  • core.controller(
  • name,
  • function( $scope, $injector ) {
  •  
  • var cacheKey = ( "cache_" + name );
  •  
  • var Constructor = constructors[ cacheKey ];
  •  
  • // If the cached constructor hasn't been built
  • // yet, invoke the factory and cache the
  • // constructor for later use.
  • if ( ! Constructor ) {
  •  
  • Constructor
  • = constructors[ cacheKey ]
  • = $injector.invoke( controllerFactory )
  • ;
  •  
  • }
  •  
  • // By returning something other than _this_,
  • // we are telling AngularJS to use the following
  • // object instance as the Controller instead of
  • // the of the current context (ie, the Factory).
  • // --
  • // NOTE: We have to pass $scope through as an
  • // injectable otherwise the Dependency-Injection
  • // framework will not know how to create it.
  • return(
  • $injector.instantiate(
  • Constructor,
  • {
  • "$scope": $scope
  • }
  • )
  • );
  •  
  • }
  • );
  •  
  • // Return the core to continue method chaining.
  • return( core );
  •  
  • };
  •  
  • // Overwrite the Angular-provided factory.
  • core.factory = factory;
  •  
  • })( app, app.factory );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Define the base-controller; since this is not a name-spaced
  • // controller, it will be routed to the underlying, core
  • // factory method.
  • app.factory(
  • "BaseController",
  • function() {
  •  
  • function BaseController( $scope ) {
  •  
  • return( this );
  •  
  • }
  •  
  • BaseController.prototype = {
  •  
  • getFoo: function() {
  •  
  • return( "Foo ( from BaseController )" );
  •  
  • }
  •  
  • };
  •  
  • return( BaseController );
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Define the "sub-class" controller; since this is a name-
  • // spaced controller, it will be routed to the wrapper factory
  • // that will proxy the controller instantiation.
  • app.factory(
  • "demo.SubController",
  • function( BaseController, $document ) {
  •  
  • function SubController( $scope, $document ) {
  •  
  • BaseController.call( this, $scope );
  •  
  • // Store foo/bar for use in the View-model.
  • $scope.foo = this.getFoo();
  • $scope.bar = this.getBar();
  •  
  • // Add some other injectables, just to make sure
  • // the factory wrapper didn't screw up the
  • // dependency-injection framework.
  • $scope.title = $document[ 0 ].title;
  •  
  • }
  •  
  • // Extend the base controller.
  • SubController.prototype = Object.create( BaseController.prototype );
  •  
  • // Add sub-class methods.
  • SubController.prototype.getBar = function() {
  •  
  • return( "Bar ( from SubController )" );
  •  
  • };
  •  
  • // Override base method; decorate the value provided
  • // by the super-class.
  • SubController.prototype.getFoo = function() {
  •  
  • return(
  • BaseController.prototype.getFoo.call( this ) +
  • "( overridden by SubClass )"
  • );
  •  
  • };
  •  
  • return( SubController );
  •  
  • }
  • );
  •  
  •  
  • </script>
  •  
  • </body>
  • </html>

When I run the above code, I get the following page output:

Using Base Controllers In AngularJS

Foo: Foo ( from BaseController )( overridden by SubClass ).

Bar: Bar ( from SubController ).

As you can see, the "Bar" value was taken directly from the sub-class. The "Foo" value, on the other hand, was taken from the sub-class, but the value provided was ultimately a decorated value from the base-class.

While I was very interested in creating a Base Controller when I first got into AngularJS, now that I've been using AngularJS for about a year, I am not sure that a Base Controller would actually add any value. With the way that $scope is used, your controller typically has to re-create all "public" methods with each Controller instance; as such, the function sharing provided by prototypal inheritance doesn't really get leveraged properly in a Controller context. That said, I am happy that I finally got a proof-of-concept working!




Reader Comments

Nice article and nice solution.
But tell me why you even need inheritance in angular's controller?
Angular's controllers are simple functions not classes, so you can't inherit them.
Controlles best practices says that your controller should be as small as possible, but your solution is making controllers so big and adds more boilerplate code to it.
If you need to share some common code between controllers (some sort of inheritance), why you're not using Services? I think that's the right tool.

Reply to this Comment

It is hard to believe this is the only way, however that being said I am glad you decided to experiment and do it! I disagree with previous comment and definitely think a more built in way to do prototypal inheritance is needed. There are definitely cases where methods on the scope or other methods are needed across controllers, and what are you left with? creating services and sending a reference to the scope to be manipulated there instead. The only other examples i've seen so far are not realistic in that the controller functions are all global and pollute the namespace. It seems like that just like you can do

var module = angular.module('myModule');

and you get a module back, why can't we at least do something like this?

var controller = module.controller('ControllerName');

and when you don't pass the constructor, you get the constructor back?

it's extremely frustrating that there is not a good way using existing angular conventions that there is not a good way to do this.

Reply to this Comment

Hey, I need help.
I am working with SQL Server 2008, MVC.NET 4.0 and EF4.0 for a Single Page Application.

I have a form that has multiple dropdownlists (say 5).
The dropdown gets populated using LINQ query from the database. It returns the list as "items"

Now in the Front-end I am populating the values in the dropdownlists using ng-model="item.projectId"

Now when I submit the form, its quite obvious that the projectId will
be posted to the server due to the double-binding nature of ng-model
directive.

I want to get the selected (user selects while filling up the form) values collectively (possibly into an array ) and send it to the controller.cs file where there an array to consume those values and
store that in the table. Can anybody give me tips on this?

Reply to this Comment

@Masih,

When I first got into AngularJS, I wanted to use base-controller because that's how I write everything on the server-side; BaseController's group together features that all controllers can use. In an AngularJS context, I did have things that I thought could be shared; specifically, this had to do with handling deferred results. I wanted to create something like:

this.handlePromise resource.get(), callback, errorCallback );

Now, the reason I wanted that to be shared is because I wanted the core "handleDeferred" to actually wrap my callbacks with references to "this". Something like:

wrapper = function(){ callback.apply( self, arguments ); };

This way, the shared controller methods could take care of the binding of methods to Controller instances. Plus, our application has a special Deferred class which extends the core AngularJS $q class to allow for cached data:

https://github.com/bennadel/DeferredWithUpdate.js

Now, with the complexity of creating base-controller, we actually did end up going with services / help classes.

That said, my Controllers are, in many cases, very complex. Granted, I probably put too much logic in them; but, at the same time, my User Interfaces are very complex as well. Lots of states and minor interactions.

I'm sure I can clean them up a bit; but I'm still learning :)

Reply to this Comment

@Kelly,

It is a bit surprising that there's not something a bit more baked-in for it. Especially when you consider that the $scope and the Controller used to be one-in-the-same (at least from what I've read). In that case, Controllers *would* necessitate prototypal inheritance; although, that said, that original design may be why they couldn't allow for it - if they [AngularJS] had to make sure that the Controller inheritance chain worked like the current $scope chain, they *couldn't* allow you to monkey with the inheritance as it would break their workflow.

So, maybe the lack of Controller insight is just a left-over of their original approach?

Reply to this Comment

I agree with @Masih and I agree with @Kelly! With AngularJS it seems you can have your cake and eat it. The service pattern is the agreed way to share code between controllers, but sometimes you want to lock down the service-controller relationship as a base class one.

But I don't think you need a custom factory. Create the service in the usual way:

  • app.factory("BaseController",function() {
  • return function ( $scope ) {
  • this.getFoo = function() {
  • return( "Foo ( from BaseController )" );
  • }
  • return( this );
  • }
  • });

Inject it the usual way:

  • app.controller("demo.SubController",
  • function( $scope, BaseController ) {
  •  
  • // this call locks down the relationship
  • BaseController.call( this, $scope );
  •  
  • // and this one depends on it
  • $scope.foo = this.getFoo();
  •  
  • // extend here...
  •  
  • });

If you want to share methods across different templates, add them to the scope in the base controller.

Reply to this Comment

@Phil, Yes, this will partially handle things. I guess I just didn't want to have to map things to the scope all the time. calling the constructor of BaseController, if I understand your suggestion correctly, will inherit those methods, but not prototypically, meaning that every instance will have a new instance of those methods instead of being shared on the prototype.

also another interesting way that I have working code for is in this article:

http://blog.omkarpatil.com/2013/02/controller-inheritance-in-angularjs.html

using injector.invoke allows for different dependency injections in the base controller and the child controller.

for now I am resigned to creating services. I may still write my controllers in a more OO way even though inheritance isn't happening..

Reply to this Comment

Hello Sir,

I have figured out the main problem.

I want to send data to server using ng-model through an array.

So it should be something like:
ng-model=selectedProjects[items];

the array would contain list of projectIds.

But I am populating the dropdown using the ng-model. So a conflict occurs.

How do I do it?

Code:

http://plnkr.co/edit/oy073r?p=preview

Reply to this Comment

Hi @Tushar,

I know it's been a while and maybe you already solved your issue but I'll respectfully tell you this:

Considering your question is not in relation with this blog post, I don't think it is the right place to ask this here, and it probably is the reason why no one (seems) to have answered you yet. It is better to stay on the subject so it's easier to discuss about specific topics;

I would recommend you to go over Google Groups on AngularJS' group at this address: https://groups.google.com/forum/#!forum/angular

The community there is more than helpful and is definitely more of a better place to ask this kind of question.

I hope this helps you in the future

Reply to this Comment

@Kelly, @Ben, @Phil:

So I am reading this post/comments as well as the link provided by Kelly about using $injector.invoke() .... in the end I am not sure what would be the consensus regarding what could/should be considered a best practice to achieve this kind of functionality

I am basically looking for prototypal inheritance across controllers too, and from these articles I understand it's not going to happen; Now, should I use a Factory as described as Phil, or $injector.invoke ? Both methods seems great in their way, despite the lack of having functions on the prototype of the baseController, but what would be the plusses and minuses of wither methods?

Thanks

Reply to this Comment

@Olivier,

> "I am basically looking for prototypal inheritance across controllers too, and from these articles I understand it's not going to happen"

My hack of creating the controller inside a "factory" does give you *true* prototypal inheritance in the Controller instances. That said, it's clearly not the way AngularJS intended controllers to be used (otherwise they would have built it that way).

This is where I really start to get torn between ease-of-use and memory-efficiency. Clearly, using prototypal inheritance is a memory-plus since you don't have to re-create all your Functions every time a controller is instantiated; but, putting all your methods in the controller is just *easier*.

In the long run (before I had this hack), I moved stuff into Services that could be injected into the Controller, and that's been working out ok.

Reply to this Comment

@Ben,

Thanks for your reply

Yes I'm kind of in the same boat/dilemma

I gave a quick try to the injector.invoke method, which seems to work relatively well but I think I did notice a performance drop as well as some higher CPU usage in Chrome (haven't tested other browsers)

That said, services and factory might be the "way to go" indeed; thing is that I need access and manipulate the $scope of my controllers and would have liked not to have to pass the object to the service's method and what not

Reply to this Comment

@Olivier,

Good luck. I'm definitely finding that with AngularJS, you get away with anything on smaller pages; but, when pages grow to an unfortunate size (when a UI isn't so great), you really have to pay attention to where tiny performance hits actually add up.

I've become good friends with the "Time Line" profiler in Chrome Dev Tools and trying to weed out where lots of Paints / Layouts are forced by various Directives.

It's fun, but frustrating to worry about things at that level.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.