Monthly Archives: February 2013

Javascript: assembly language for the web

Javascript has been called the programming language of the web. This is almost true. Javascript is more like an assembler. Almost every meaningful framework is dominated by a layer of infrastructure to manage modules, references, dependencies and so on.

The result is always very noisy code that is just harder to read.

What a world it would be if Javascript had macros and eschewed the Algol syntax and opted for Lisp style parens. JSON? It’s all Lisp.

Angular Dependency Declarations: too many ways to screw it up

Because Angular’s modules and dependencies are layered on top of the Javascript language they suffer from a fairly verbose form. There are several ways to declare a dependency and they are documented in the obvious-in-retrospect $injector documentation.  However, only one of these is acceptable in my book.  The one true way to declare an Angular provider:

angular.module("SomeModule").service("SomeService", ["Dep1", "$rootScope", "Dep2", function SomeService(Dep1, $rootScope, Dep2) {
// your code here
}]); // such an elegant way to close the declaration!

This is almost DRY.  At least the repeated declarations are close to each other and could probably be verified by a lint checker.

Why do the other forms exist at all?  The Angular devs correctly realized that for someone’s first experience with Angular the above is just too syntactically noisy. They came up with a very clever trick that works well for fiddles and plunks but breaks confusingly when used in any real production environment. The trick is to check if the dependency list passed to module.service is an array or a function. If it’s an array then it’s presumed to have a list of dependencies and a function. Oh the overloading! Why not just put the function as a 3rd parameter? If it’s just a function then Angular gets the source of the function (yes, you can call fn.toString()) and then parses out the declared argument names and treats those as dependencies to be injected.

module.controller("MyController", function ($scope) {
// some code
});

It would be great if this just worked all the time. Unfortunately minifiers will replace all argument names with single letter arguments and Angular will spit out a confusing error message:
Error: Unknown provider: aProvider at Error ()

The “aProvider” here is the function argument name “a” and “Provider” (which gives you a hint as to how Angular manages its namespace).

Angular Module Declarations Are Error Prone

Sometimes it’s the little things that can really trip you up. This particular one comes up on IRC often enough that I decided to write it up.

The most common error is:

Error: Unknown provider

The most common cause is an incorrectly declared or referenced Angular module.

Angular is built on a system of modules, providers and dependency injection.  As always with Javascript this is all done using functions to construct internal data structures that have no special meaning to the language itself.  This means that errors are usually opaque and deep inside angular code.

Here’s how you declare an Angular module.

angular.module("ModuleName", ["ModuleImport1", "ModuleImport2"], optionalConfigFn);

And here’s how you reference a module so you can declare providers (e.g. services):

angular.module("ModuleName")

The above is necessary when a module’s providers are in multiple files.

The reality is that many developers have only one module and so their module declaration looks like this:

angular.module("ModuleName", [])

And so a very common mistake is to reference a module as follows:

angular.module("ModuleName", []);

I think this is because many people also declare a module the same way, with no other module references. If you do this then Angular will moronically create another module and lose the original module. I’d love to understand for which scenarios this makes sense. My first pull request to Angular will be to turn that into an exception.
Another solution would be to avoid overloading:

angular.declareModule("ModuleName", dependencyArray, optionalConfigFn);

angular.findModule("ModuleName").service("myService", [function() {}]);