Tuesday, September 9, 2014

Angular constants vs services

A few months ago I've started using a pattern to wrap library code as angular constants to make them injectable.  I'd handle something like d3 with:

app.constant('d3', window.d3);

This was easy, but I always felt a little weird doing this because these library objects are not really constants.  Depending on the library, these can either be factory functions or large complex objects with a huge amount of internal state.  Definitely not constant.

A co-worker was wrapping some simple code to inject into a route handle configuration block.  The code was just a simple function for generating a template string based on a string passed to it.  This sounds like a great place to use a factory, but the docs say that
Only providers and constants can be injected into configuration blocks.
So this made me think again about constants.  Why could they be injected early?

The docs have a little to say about this.  See "the guide" documentation on modules:

Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants can be injected into configuration blocks. This is to prevent accidental instantiation of services before they have been fully configured. 
Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be injected into run blocks. This is to prevent further system configuration during application run time.

The module type documentation also hints at the difference:

constant(name, object);

Because the constant are fixed, they get applied before other provide methods. See $provide.constant().
But this talk of "fixed" and "system configuration" is a little misleading.  Looking at the code for the injector, it starts to become clear what they really mean to say is that constants don't have any async configuration.

When a constant is set up, the constant function simply checks the name for validity and shoves the object into a couple cache arrays.

However, when a service is instantiated, it starts with a call to the service function, which calls factory, which calls provider.  Because services call provider (by way of factory) with an array of arguments, they are set up with a call to providerInjector.instantiate.  This method makes a call to the getService method of createInternalInjector.  This function is where the async handling magic happens.  Because the call to the factory constructor method for a service can be async, this function sets a marker at the assigned position of that service in the cache to prevent the service from getting instantiated multiple times when the thread of control gets passed back to the main process which is injecting other modules.

Check it out.  It's pretty neat.

After seeing all the complexity hoops that angular jumps through to make services work (and how simple constants are by comparison), the difference becomes clear.  If you use services, angular handles aync for you.  If you're using constants, you're on your own.