This is how AngularJS announces itself on its home page:
HTML enhanced for web apps!
Hm, ok, but not terribly informative. Here’s the next paragraph:
HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop.
That’s better. Let me try to ease you into this “framework” on which HouseMon is based:
- Angular (“NG”) is client-side only. It doesn’t exist on the server. It’s all browser stuff.
- It doesn’t take over. You could use NG for parts of a page. HouseMon is 100% NG.
- It comes with many concepts, conventions, and terms you must get used to. They rock.
I can think of two main stumbling blocks: “Dependency Injection” and all that “service”, “factory”, and “controller” stuff (not to mention “directives”). And Model-View-Something.
It’s not hard. It wasn’t designed to be hard. The terminology is pretty clever. I’d say that it was invented to produce a mental model which can be used and extended. And like a beautiful clock, it doesn’t start ticking until all the little pieces are positioned just right. Then it will run (and evolve!) forever.
Dependency injection is nothing new, it’s essentially another name for “require” (Node.js), “import” (Python), or “#include” (C/C++). Let’s take a fictional piece of CoffeeScript code:
value = 123 myCode = (foo, bar) -> foo * value + bar console.log myCode(100, 45)
The output will be “12345″. But with dependency injection, we can do something else:
value = 123 myCode = (foo, bar) -> foo * value + bar define 'foo', 100 define 'bar', 45 console.log inject(myCode)
This is not working code, but it illustrates how we could define things in some more global context, then call a function and give it access to that context. Note that the “inject” call does something very magical: it looks at the names of myCode’s arguments, and then somehow looks them up and finds the values to pass to myCode.
That’s dependency injection. And Angular uses it all over the place. It will often call functions through a special “injector” and get hold of all args expected by the function. One reason this works is because closures can be used to get local scope information into the function. Note how myCode had access to “value” via a normal JavaScript closure.
In short: dependency injection is a way for functions to import all the pieces they need. This makes them extremely modular (and it’s great for test suites, because you can inject special versions and mock objects to test each piece of code outside its normal context).
The other big hurdle I had to overcome when starting out with Angular, is all those services, providers, factories, controllers, and directives. As it turns out, that too is much simpler than it might seem:
- Angular code is packaged as “modules” (I actually only use a single one in HouseMon)
- in these modules, you define services, etc – more on this in a moment
- the whole startup is orchestrated in a specific way: boot, config, run, fly
- ok, well, not “fly” – that’s just my name for it…
The startup sequence is key – to understand what goes where, and what gets called when:
- the browser starts by loading an HTML page with
<script>
tags in it (doh) - all the code needed on the browser side has to be loaded up front
- what this code does is define modules, services, and so on
- nothing is really “running” at this point, these are all function definitions
- the last step is to call
angular.bootstrap(document, ['myApp']);
- at this point, all the
config
sections defined in the modules are run - once that is over, all the
run
sections are run, this is the real application start - when that is done, we in essence enter The Big Event Loop in the browser: lift off!
So on the client side, i.e. the browser, the entire application needs to be structured as a set of definitions. A few things need to be done early on (config), a few more once everything has been defined and Angular’s “$rootScope” has been put in place (read: the main page context is ready, a bit like the DOM’s “document.onload”), and then it all starts doing real work through the controllers tied to various HTML elements in the DOM.
Providers, factories, services, and even constants and values, are merely variations on a theme. There is an excellent page by Matt Haggard describing these differences. It’s all smoke and mirrors, really. Everything except directives is a provider in Angular.
Angular is like the casing of a watch, with a powerful pre-loaded spring already in place. You “just” have to figure out the role of the different pieces, put a few of them in place, and it’ll run all by itself from then on. It’s one of the most elegant frameworks I’ve ever seen.
With HouseMon, I’ve put the main pieces in place to show a web page in the browser with a navigation top bar, and the hooks to tie into Primus and RPC. The rest is all up to the modules, i.e. the subfolders added to the app/
folder. Each client.coffee
file is an Angular module. HouseMon will automatically load them all into the browser (via a /primus/primus.js
file generated on-the-fly by Primus). This has turned out to be extremely modular and flexible – and it couldn’t have been done without Angular.
If you’re looking for a “way in” for the HouseMon client-side code, then the place to start is app/index.jade
, which uses Jade’s extend mechanism to tie into main/layout.jade
, so that would be the second place to start. After that, it’s all Angular-structured code, i.e. views, view templates with controllers, and the services they call.
I’m quite excited by all this, because I’ve never before been able to develop software in such a truly modular fashion. The interfaces are high-level, clean (and clear) and all future functionality can now be added, extended, replaced, or even removed again – step by step.