Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Hemant Khandelwal
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Hemant Khandelwal@khandelwalh )

Directive Controllers Cannot Use The Revealing Module Pattern In AngularJS

By Ben Nadel on

This is just a quick blog post (more of a note-to-self) to demonstrate the fact that you cannot use the revealing module pattern with Directive Controllers in AngularJS. And, just to be clear, all controllers, in AngularJS, are "directive controllers." Meaning, the ngController directive is nothing more than a directive that instantiates a "directive controller" using a lookup value instead of a raw function reference.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

Directive controllers cannot use the revealing module pattern because they are created using a two-step process. First, the instance is created; then, the constructor is invoked. However, the return value of the constructor is never used to overwrite the instance value. As such, your controller's return value is ignored. More or less, the steps looks like this (paired down):

  1. var instance = Object.create( controllerPrototype );
  2. $injector.invoke( controllerFunction, instance, locals, constructor );
  3. return( instance );

As you can see, the result of the controller invocation (using .invoke()) is ignored. Only the Object.create() value is returned.

To see this in a demo, take a look at the following code. Notice that my directive Controller is attempting to return a new API surface area:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Directive Controllers Cannot Use The Revealing Module Pattern In AngularJS
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Directive Controllers Cannot Use The Revealing Module Pattern In AngularJS
  • </h1>
  •  
  • <p bn-test>
  • This is a directive.
  • </p>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.3.15.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I am a demo directive - I don't actually provide any behavior.
  • app.directive(
  • "bnTest",
  • function() {
  •  
  • // Return the directive configuration object.
  • // --
  • // NOTE: We are using a lexically-bound Controller constructor to define
  • // the controller for our directive. The demonstrated behavior, however,
  • // would not change even if we were using a .controller() reference.
  • return({
  • controller: TestController,
  • link: link,
  • require: "bnTest",
  • restrict: "A"
  • });
  •  
  •  
  • // I am the Controller for the directive.
  • function TestController( $scope ) {
  •  
  • var thing = "woot!";
  •  
  • // Return public API.
  • // --
  • // *************************************************************** //
  • // WARNING: THIS DOES NOT WORK. The instance of the Controller is
  • // creating using Object.create(). Then, the actual constructor is
  • // invoked using $injector.invoke(); BUT, the return value of the
  • // constructor is not used to overwrite the instance reference.
  • // *************************************************************** //
  • return({
  • getThing: getThing
  • });
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • function getThing() {
  •  
  • return( thing );
  •  
  • }
  •  
  • }
  •  
  •  
  • // I bind the JavaScript events to the local scope.
  • // --
  • // NOTE: The TestController instance is being injected as the fourth
  • // argument since our directive configuration object "required" it.
  • function link( scope, element, attributes, controller ) {
  •  
  • console.info( "bnTest Link Controller" );
  • console.log( controller );
  •  
  • }
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

When the directive finally links and we log the passed-in controller instance, we get the following console output:


 
 
 

 
 Directive controller cannot use the revealing module pattern in AngularJS. 
 
 
 

Notice that the object is empty - it doesn't contain the methods that we returned in the constructor.

Normally, I would never think to use the revealing module pattern with an AngularJS controller; but, when you define your controller within the lexical bounds of the directive factory, it's hard to remember that it's still a "normal" Controller. And, that it lives and dies by the same rules as every other controller.




Reader Comments

@Vijay,

Good point, I generally use the module to define my controllers. That said, this is not a "global" controller - it's actually contained within the lexical context of the bnTest directive. So, it is a function reference, rather than a module-lookup; but, it won't leak outside the bounds of the directive due to scoping. So, it's _not as bad_ as it could be :D

Reply to this Comment

@André,

Oh, great find! I still haven't really looked into AngularJS 1.4 yet. Things move so darn quickly and I tend to not look at things until they actually get released (meaning, I sort of ignore the betas and RCs). There's just so darn much stuff out there to learn :)

I'm also very curious to learn more about TypeScript. I was just listening to an Adventures in Angular podcast and the group was talking about TypeScript in AngularJS 2.0. It was an interesting discussion; they were all sort of agreeing that TypeScript made a lot of sense for some things, like Services and Controllers. But, that it seemed to make other things like Providers and Directives harder to create.

All I know, so far, is what I've seen in a few presentation. But, I'm excited to try it out.

Reply to this Comment

I didn't got my hands at Angular 2 to see how things are going, but now, while I believe in the value proposition of TypeScript, I think it misses a few oportunities like taking advantages of closures. I really like the ability to consume .d.ts and native (global members in .ts) declarations though, it speeds up coding and refactorings.

Not sure at how TypeScript make things harder as I do all kinds of code with it, but I learned each kind of object follows a different pattern, like storing dependencies in service objects, or give up typing.

And annotations shall be the attributes in static languages made right :)

Reply to this Comment

@André,

Well, I'm definitely looking forward to giving it a try. I've been a bit distracted by Node.js lately (since we're starting to use it at work). But, will be digging back into AngularJS again soon.

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.