ui.router and 'aProvider' problems

So I have a fairly simple AngularJS project using the new Angular UI Router and the project is scaffolded with the Yeoman angular generator. All is well when running locally in developer mode, but theres trouble once I run grunt and run with the grunt built/minified/uglified code – the browser would just hang (Safari) or outright crash (Chrome). Hmmm, thats definitely not right.

My scripts are included in my index.html file like so:

<!-- build:js({.tmp,app}) scripts/scripts.js -->
<script src="scripts/app.js"></script>
<script src="scripts/controllers/main.js"></script>
<script src="scripts/controllers/realm.js"></script>
<script src="scripts/services/realms.js"></script>
<!-- endbuild -->

The purpose of the comment lines are to delineate a block of the html that should be processed together – in this case the individual files will be concatenated, minified, and then the index.html will be rewritten to include the processed file scripts.js

After some non-productive mucking about with ordering and including/excluding files for processing, I cracked open the processed file. It’s still fairly readable even with the minification that has taken place.

NgMin is cool

One thing the angular generator set up is the use of ngMin so you can write your Javascript without having to worry about details of how it will be minified later so that this:

angular.module("indexApp",[
    "chieffancypants.loadingBar","ui.bootstrap","ui.router"
  ])
  .config(function($urlRouterProvider, $stateProvider) {
    // snip...
  });

is turned into this after grunt is done with it:

angular.module("indexApp",[
    "chieffancypants.loadingBar","ui.bootstrap","ui.router"
  ])
  .config(["$urlRouterProvider","$stateProvider",function(a,b) {
    // snip...
  }]);

Notice the arguments to the anonymous function passed to the config method in the before example. The whole argument has been rewritten as an array with the names of the arguments as strings followed by the function where the arguments have been replaced with the more succinct names “a” and “b”.

This is cool. NgMin is aware of how Angular does it’s dependency injection and rewrites it using the more verbose syntax Angular supports so we can still use our other tools for processing scripts.

But not all is well.

Here is one of my states that is setup in the anonymous funciton passed to the module’s config method:

$stateProvider
  .state('main', {
    url: '/',
    templateUrl: 'views/main.html',
    controller: 'MainCtrl',
    resolve: {
      allTheRealms: function(MyAppService) {
        console.log('MainCtrl resolve', arguments);
        return MyAppService.init();
      }
    }
  })

I’m using a service to make https requests against a service and this needs to be done on initialization so I’m using the $stateProvider resolve property to initiate the asynchronous call to the server and once that succeeds its passed to my controller.

This normally works great, but in this case the resolve function had its own dependency on a service. After Grunt had done its thing the code above looked like this:

b.state('main', {
  url: '/',
  templateUrl: 'views/main.html',
  controller: 'MainCtrl',
  resolve: {
    allTheRealms: function(a) {
      return a.init();
    }
  }
})

Notice the function argument is now just called a, and the $stateProvider is called b. This is not helpful as Angular’s dependency-injection magic doesn’t know what the function needs (i.e. I have no “a” service to be injected). Usually this files with an error about no “aProvider” found, but in this case the browser was hanging and or crashing!

What is a?

Step back to the earlier example:

angular.module("indexApp",[
    "chieffancypants.loadingBar","ui.bootstrap","ui.router"
  ])
  .config(["$urlRouterProvider","$stateProvider",function(a,b) {
    // snip...
  }]);

a is actually the $urlRouterProvider and we are calling init() on it. Not what we wanted at all. I haven’t dug further but my guess is there is an init method on that and it’s resulting in something nasty and probably recursive causing the Bad Things™.

Well once I saw that it was a quick Google of “ui.router ngmin resolve” and found this issue: Support for ui-router resolve, onEnter and onExit

So apparently ngMin for all its goodness misses on these properties in the state setup. The simple solution is go back to using the alternate Angular syntax for specifying the dependencies:

$stateProvider
  .state('main', {
    url: '/',
    templateUrl: 'views/main.html',
    controller: 'MainCtrl',
    resolve: {
      allTheRealms: ['MyAppService', function(MyAppService) {
        console.log('MainCtrl resolve', arguments);
        return MyAppService.init();
      }]
    }
  })

Once I had that sorted my app loaded fine!

Filed under