Why I’ve stopped doing single-line-define-and-assign on Angular $scope

I’ve been using Angular for all of my rapid-prototyping work for almost three years now. I’m very comfortable with the Angular idiom. But there’s one piece of it that I’ve entirely abandoned as bad hygiene and broken functionality (at least when mixed with ‘function statements’): the ‘single-line-define-and-assign’ pattern for variables and functions we want to expose via Angular’s $scope:

$scope.myVar = 12345;
$scope.myFunction = function () {/* do stuff */};

Instead, I do it like this:

var myVar = 12345;
function myFunction() {
  /* do stuff */
}

$scope.myVar = myVar;
$scope.myFunction = myFunction;

<screaming ensues> I know, I know. “Ur doin’ it wrong!” But read on, and scream after.


Quick background: Angular’s $scope construct provides us with quite a few super-useful benefits, all via one implementation detail:

  • a convention for exposing public models and methods into our views (while keeping other items private)
  • implicit inheritance of models and methods between nested controllers
  • an event-bus for explicit communication between scopes/$scopes.
  • dependency-injection of all those utilities into any consuming execution context, as an aid to both composition and testing

Some folks have argued that some of those features are actually a bit of a double-edged sword. But dangerous and useful are two different concerns, and I think the danger is overstated in any case. So, I’m not here to argue that there’s a problem with Angular’s $scope pattern itself. Just with one particular syntax for interacting with it.

We’re all taught from day one with Angular to define our public variables and methods at the same time that we assign them onto the $scope:


$scope.myVar = 12345;
$scope.myMethod = function(param) {
 // do stuff
 }

That syntax works. Mostly. But you don’t *have* to do that way. There’s no functional requirement to do so. I now believe that the 5 seconds we save by doing so isn’t worth the various downsides (unless your whole team has orders to use ‘function expressions, everywhere, at all times):


Downside #1: Ugly, hard-to-read Code. We’re using multiple syntaxes for writing JavaScript within Angular, and even *within the same controller*.

It’s bad enough that we have to argue with anti-framework luddites about Angular being somehow inauthentic to the JavaScript ecosystem, as if there’s something empowering about re-writing your own boilerplate over and over and over again for every application. In truth, nearly all Angular syntax is simply an extension or customization of good patterns that you might use anywhere in JavaScript. But this particular pattern of single-line-define-and-assign (henceforth: ‘SLDAA’) is different.

To see why, take a look at the ‘Revealed Module Pattern’, one of the most important patterns in functional programming. When we use a function rather than an object as an execution context, we often want a way to return an interface to our public properties out to the calling context, while keeping other internal implementation properties private. So, we ‘reveal’ an object as the return from our function, containing those carefully-selected public properties, like so:


function constructorFunction(arg1, arg2, arg3) {
  // First, we write code: what this function *does*.
  // Variables, functions, and uses of them both.
  var pi = 3.12,
      variable1 = arg1 + arg2,
      variable2 = calcVar2(arg3, pi),
      variable3 = doThings(variable2),
      publicObject = {};

  function calcVar2(number, constant) {
    return number/constant;
  }
  function doThings(param) {
    // do stuff
    return result;
  }
  function doMoreThings(params) {
    // do moar stuff
  }

  // Then, we write the interface that this function will expose
  // to the calling context:
  publicObject.thingsFunction = doThings;
  publicObject.moreThingsFunction = doMoreThings;
  publicObject.value = variable2;
  return publicObject;
}

In that example, we declare and interact with our private/internal functions and variables (like calcVar2()) in exactly the same way we declare and interact with the ones that will eventually be public (like doThings()). Reading through the module later, trying to understand it so we can maintain and extend it, we can focus clearly on the implementation details and how they work, and then focus on the public interface, which is defined in a single block at the end. The two are entirely independent, we can even name the public interfaces differently than the internal properties, and we haven’t contaminated one with the other. We have both logical and visual-spatial separation of concerns.

But not in an Angular controller where we’re doing single-line-define-and-assign. In that syntax, we instead have something more like:


angular.module('app').controller('myController', function (arg1, arg2, arg3) {

  var pi = 3.12,
      variable1 = arg1 + arg2,
      variable2 = calcVar2(variable1, pi);

  $scope.variable3 = $scope.doThings(variable2);

  function calcVar2(number, constant) {
    return number/constant;
  }

  $scope.doThings(params) {
    // do stuff
  }
  $scope.doMoreThings(params) {
    // do moar stuff
  }

})

We end up with two different syntaxes for doing the same things. Things that should look and work the same. A function should be a function. A variable should be a variable. But not with single-line-define-and-assign. With SLDAA, we’re defining both variables and functions with two different syntaxes each. We also need to use two different syntaxes for referencing/invoking, even though we’re defining and invoking/referencing in the same execution scope. Where we invoke private functions with a simple functionName(), all of our public functions now have to be invoked with $scope.functionName(), even though we haven’t left the defining context. That’s just… unhelpful. But there’s another, demonstrably better way.

Instead, we can use standard JavaScript syntax based on the tried-and-true revealed-module pattern. It produces code that’s vastly clearer and more readable, and better aligned with standard, non-Angular JavaScript conventions, and without changing anything else about how Angular works. To do so, just remember one key thing: Angular is going to return the public $scope object for us, instead of us needing to create and return our own publicObject. Which means we can create an Angular controller that looks almost exactly like the equivalent non-Angular constructor, but minus the explicit return:


angular.module('app').controller('myController', function (arg1, arg2, arg3) {

  var pi = 3.12,
      variable1 = arg1 + arg2,
      variable2 = calcVar2(arg3, pi),
      variable3 = doThings(variable2);
      // definition of `$scope` (in lieu of `var publicObject`)
      // happens at injection time. 

  function calcVar2(number, constant) {
    return number/constant;
  }
  function doThings(param) {
    // do stuff
    return result;
  }
  function doMoreThings(params) {
    // do moar stuff
  }

  $scope.thingsFunction = doThings;
  $scope.moreThingsFunction = doMoreThings;
  $scope.value = variable2;
  // and now there's an implicit:
  // return $scope;

}

In this version, calcVar2() and doThings() are both declared and invoked with the same syntax inside the controller, even though one is public and one is not. Similarly, variable1, variable2, and variable3 are all declared and referenced in the exact same way, even though some are private, and one is public. This is all exactly as it should be. It’s clear, it’s concise, and it’s standard JavaScript.

That (Reason #1) really should be all the justification we need to drop the SLDAA pattern, since we all should be looking to optimize for readability and maintainability, not 5 fewer seconds of typing. But Reason #2 is the icing on the cake, making it clear that the same syntax variations that mess with our heads also mess with execution:


Reason #2: SLDAA breaks JavaScript function-hoisting, messes with our stack-traces, and produces unexpected results at run-time.

I invented ‘SLDAA’ as an abstraction for the use of this syntax with both functions and simple variables. (If there’s better, canonical term for this, please share in comments.) But when it comes to functions, what we’re really talking about is the eternal effort to properly distinguish between function declarations/statements and function expressions. Using SLDAA with a function in Angular makes that statement into a function expression, rather than a function declaration/statement. (See the links above for clarification about the difference.)

Which is fine… if it’s intentional. See, I’m not here to try to litigate the question of expressions vs. declarations. There are excellent reasons for using both forms – and even mixed in the same application… provided that you do so consciously. (Full disclosure: My own preference is to go with the grain of Javascript, roll with variable/function hoisting, and thus stick with function declarations/statements. YMMV.)

But the bottom line on SLDAA in Angular is that it forces most people to intermingle function expressions and function declarations against their will, and without awareness of the consequences. You could, conceivably, change all of your non-$scope-d functions to be function expressions too. As long as you at least pick one pattern and stick to it religiously, you’ll be fine. But whichever way you lean, why let Angular dictate that pattern to you, when you can use the more flexible, more readable pattern I’ve suggested above? In particular, why let Angular dictate that pattern to our junior devs, who won’t understand the consequences?

I’ve had to debug enough flaky controllers using SLDAA – including my own, at times – to know this for a fact. Most folks don’t get it, and aren’t about to start. The consequence of mixing function statements and function expressions in a controller (without documenting the hell out of the reason for the intentional discrepancy) is that things execute in an order other than the developer intended, because function statements are hoisted at parse time and function expressions are not. It can produce very subtle errors that you’ll have to fix by making your code even uglier than it already was.

Plus, using SLDAA means you’ll get names for some of your functions in stack traces and not for others: again, inconsistency galore.

<hr />

You can make SLDAA work about as well as the way I’ve suggested, if you’re careful to:

  • use function expressions everywhere
  • name your functions in those expressions, instead of assigning anonymous functions to named properties
  • make appropriate changes to the sequencing of declarations, assignments, and invocations.

You can do that. Your behaviors will at least be consistent that way, if you’re aggressive in policing the style of your whole team. And your syntax is at least consistent within your controllers that way, assuming you assign all of your internal variables to model objects, even if you don’t need them on $scope.

But to me, as a full-time javascripter, it just feels much less concise, and it feels to me like you’re fighting against the grain of Javascript. And, unless you work very hard to consistently enforce the viable version of the SLDAA approach to function expressions and model objects with your entire team (including both the Javascript natives and those coming from other languages) using SLDAA will produce code that’s both unreadable and unpredictable. In the absence of such enforcement, you’ll be far better off abandoning it entirely, and achieving consistency around function statements and adaptation of the revealed-module pattern for use with $scope.


NOTE: There’s an important problem in the code samples above, which were over-simplified for speed and clarity. Did you spot it? It’s the assignment by reference of primitive values onto Angular $scope. In Javascript, assignment by reference of a variable that evaluates to a primitive (strings, numbers, booleans, etc.), results in a ‘copy’ of that primitive value, not a persistent reference. Do that inside a controller, and your $scope will hold a reference only to the original value, not to the variable that’s operative inside the controller. In other words, the value on the $scope will never update, no matter what happens.

Angular already teaches us (but not as loudly/repeatedly as it might warrant) to assign only objects to $scope, not primitives. If you assign primitives to $scope, it’ll seem to work at first, and then things will start getting sideways, particularly when there are child $scope’s involved. So if you follow that rule, and expose only objects, arrays and functions onto the $scope (so all primitives would be contained inside an object or array), the approach I describe above should work just fine. But, if you do it the way the examples above indicate, and bind directly to  primitives, you’ll get into trouble. Don’t do that. (I’ll update the examples someday to emphasize that, but I’m swamped today.)


ADDITION 3/2/15: The syntax I’ve used above for $scope assignments is a little more verbose than you’d have with a typical revealed module. We can save some boilerplate, and get within a single line (and one `return` keyword) of the non-Angular revealed-module pattern, by taking advantage of `angular.extend()`:


angular.module('app').controller('myController', function (arg1, arg2, arg3) {

  var pi = 3.12,
      variable1 = arg1 + arg2,
      variable2 = calcVar2(arg3, pi),
      variable3 = doThings(variable2),
      publicInterface;
      // definition of `$scope` (in lieu of `var publicObject`)
      // happens at injection time. 

  function calcVar2(number, constant) {
    return number/constant;
  }
  function doThings(param) {
    // do stuff
    return result;
  }
  function doMoreThings(params) {
    // do moar stuff
  }

  // Normally, this would say "**return** publicInterface", but we're dropping the  "return",... 
  publicInterface = {
    thingsFunction: doThings,
    moreThingsFunction: doMoreThings,
    value: variable2
  }
  // ... instead, we Angular-ize our revealed-module by adding all those properties to $scope.
  angular.extend($scope, publicInterface);

  // and now there's an implicit:
  // return $scope;

}
Advertisements
Tagged , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: