Monday 27 August 2012

Getting rid of the main()

Did you know that your JavaScript has a main() method? That’s right – the outermost scope of any script is the main method. It does not have an official method declaration but it is the same thing for all practical purposes.

<script>
window.alert('Greetings from the main method.');
</script>


Ok, so JavaScript has a main method, just like most languages. But Misko, you ask, what is your point? My point is that you need a main method for assembling, initializing and starting your application, but it is causing you problems and you need to get rid of it.

The application assembly is something which grows organically on any project. Usually with lots of global state and singletons. No one really sits down and designs the main() method. It is an afterthought. On a large project the main() method becomes a pit where initialization happens, but no one knows why it happens in any particular order. Worse yet, everyone is afraid to change it, since it usually results in catastrophic failure.

But the main() method has one more property, and that is that it can not be prevented from running. This usually spells doom for testing your application, since loading the JavaScript into the test harness results in running application at best, and horrible exceptions at worse. Neither of which is helpful for writing unit tests.

AngularJS applications have no main methods. Or rather AngularJS main methods are empty of logic and behavior, they only contain code declaration. This means that AngularJS applications are more testable and do not have a Gordian Knot of dependencies in the main() method.

But that begs the question: How do AngularJS applications assemble themselves? The answer lies in a declarative approach to application assembly. Rather than having a main() method responsible for instantiating and assembling all of the components of the application, an AngularJS application components simply declare which other components they need. Dependency injection system can then instantiate any components and all of its dependencies on as needed basis. Let’s look at an example:

angular.app('example').
service('wheels', Wheels).
service('engine', Engine).
service('car', Car);

function Wheels() {
// do something
}

function Engine() {
// do something
}

function Car(wheels, engine) {
// Look, car automatically gets wheels and engine.
}


The last piece which is missing is knowing which component to instantiate first to get the application going. This component is usually a root controller, and AngularJS knows how to instantiate it because ng-controller directive triggers its instantiation like so:

<script>
// $window is MyApplication controller dependency
// Asking for $window in the constructor declares it as dependency
MyApplication = function($window) {
$window.alert('hello world');
}
</script>
<body ng-controller="MyApplication">
<!-- Asking for MyApplication causes it to be instantiated with all of its dependencies --!>
</body>
Note: Alternatively $route service or a run block of a module can perform the same role.

The best part of this is that AngularJS applications are practically begging to be unit tested. Loading the JavaScript in the test harness just specifies the definitions of components, but it has no side effects. Instantiating any one component in a test is easy, one simply ask the dependency injection system for it. But the best part is that in unit-tests we can override the components and definitions and replace them with test friendlies making unit-testing AngularJS application a cinch.
Share:

0 comments:

Post a Comment