12/04/2014

The Synchronous in Angular Asynchronous Module Loading

Recently at the office, we have been removing Require.js from our base architecture and going as much Angular native as possible. This has been an extremely interesting exercise. One issue that become apparent immediately was code organization. Most code was loading as the callback function to a Require define block. With the define block gone, the scope suddenly went global and code that had been lumped together for Require callbacks no longer needed to cohabitate. The new challenge was how to organize code and keep global scope clean.

The answer for us was angular.module(). Code that had previously been anonymous functions, internally scoped functions, and angular structures kept together had to be refactored, re-arranged, and de-coupled. The way to do this was using the module function to keep everything located in the same context.

Most code examples will show code structured the following way:

angular.module ('myApp', [])
  .controller ('myAppCtrl', function(){})
  .directive ('myAppDirective', function (){})


"..until your Angular app grows significant it's an easy pattern to fall into."

Much of our code was also built in a similar way. Often this structure is succinct for demonstration purposes, and until your Angular app grows significant it's an easy pattern to fall into. We needed to break our code out so we moved all components to their own files. The file system became:

widget
  |
  |-- app.js
  |
  |-- controllers
  |    |
  |    |-- myAppCtrl.js
  |
  |-- directives
  |    |
  |    |-- myAppDirective.js
  |
  |-- providers
       |
       |-- myAppFactory.js
and the pattern changed to this for us:
file: app.js
  angular.module ('myApp', [])

file: /controllers/myAppCtrl.js
  angular.module ('myApp).controller ('MyAppCtrl', function(){});

file: /directives/myAppDirective.js
  angular.module ('myApp).directive ('MyAppDirective', function(){});

file: /providers/myAppFactory.js
  angular.module ('myApp).factory ('MyAppFactory', function(){});


"After some work we found an interesting nuance to module loading."

This isn't a new idea, and several articles have been written discussing how to organize large Angular projects. This structure did work very well for us, with one exception that I didn't see often mentioned. We immediately got the dreaded module not found error:

Module 'myApp' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.

After some work we found an interesting nuance to module loading. This was hidden since everything was either living together or the context had been instantiated elsewhere by the time require was done. Our testing missed it because karma forces synchronous script loading. It is true that module loading in Angular is asynchronous.

However, module instantiation is synchronous.

angular.module('')

will not work before the instantiation call of

angular.module ('myApp'. [])

To resolve this issue we modified our concatentation scripts so the app.js was concatenated in before the other application files. If not concatenating, then make sure your script tags load the module instantiation first.

In retrospect, this seems a little obvious. It makes sense that module instantation needs to occur before assignment. The stumbling block is that the rest of module loading code is asynchronous, and this little synchronous requirement is easy to overlook.

As is often said about Angular, if you plan on a large project with it make sure you truly understand all the shortcuts it makes under the covers.