Bad routes in Angular UI Router

I love the Angular UI project‘s Router component; state-based routing & sub-routes are fantastic! However I was having a teensy little problem with it: it was REALLY easy to mess up the parameterized URLs and the app will end up in a bad state without a really great way of addressing the problem. I explored a few different avenues, but each one I tried was more hackish than the rest. Then I took a look at my boss’ implementation for another project and decided to expand on that in a few ways.

The crux of the problem was that I am using promises from the $q service to retrieve resources. Say my url was /customers for my list-view, then /customers/[id] is the detail-view. When I would end up with the url as /customers/ the logic of the router matches against the detail-view’s URL pattern with an empty ID parameter – because THAT’s the thing I would want it to do!

Solving this required me to do a few things to lower the friction for future implementations (keeping in mind I’m going to have to do this for EVERY Url-based state I use throughout my application).

  1. My API service needed to detect and throw errors when the single-object GET method was called with an empty ID
  2. I needed to handle $stateChangeError on the $rootScope to handle the errors generated during the resolve portion of UI router’s functioning.
  3. I added a catch statement to my Promise chain from the api (return api.get(id).catch(function(err){ …});)

Steps 1 & 2 alone allow me to detect the errors and do something about them, while step 3 allows me to do something intelligent with the errors in a somewhat less repetitive way. It also means that I don’t have to put a bunch of overarching logic into the core of my application (step 2) for the various modules that will effectively know where they need to go in case of an error.

Meaning that during my dependency resolution in each step, all I really need to do is call the API, catch an error, then throw my home-grown RouteError object, which ends up looking like:

//this structure is created by `new gw.RouteError(...)` calls in the examples below
{
  status: 404,
  reason: "Couldn't find the damned page!",
  fallbackRoute: 'customers',
  routeOptions: {}
}

So inside /customers/[id]

  • I now get a sane chain of errors
  • Bad requests to the API are detected
  • The error is handled in a minimalistic way during the routing definition
  • The core has enough information to intelligently redirect all routing errors to the fallback route of their choosing

The core $stateChangeErrorhandling code looks like this:

$rootScope.$on('$stateChangeError', function (evt, toState, toParams, fromState, fromParams, error) {
  if (error.fallbackRoute) {
    $state.go(error.fallbackRoute, error.routeOptions);
  }
});

…and my state’s resolve function at least looks somewhat sane…

customer: ['$stateParams', 'customerApi', function ($stateParams, customerApi) {  
  return customerApi.get($stateParams.customerId)
    .catch(function (err) {
      if (err) {
        var status = err.status || -1,
            reason = err.data || '';
        throw new gw.RouteError(status, reason, 'customers');
      }
    throw new gw.RouteError(500, '', 'customers');
  });
}]

All in all, it seems to solve my fairly simple problem in a fairly simple way.

[edited 2014-10-23: clarified initial javascript object literal structure and connection to gw.RouteError constructor]