How to defer route definition in an AngularJS web app

Recently, for a project built on AngularJS, I found myself in front of an unusual requirement: I needed to define dinamically the routes of the web application, on the basis of a response from the server.

This post is meant to share the process I followed.

Server response

For the sake of simplicity, I assume to have the following backend service, that when called, responds with a json containing the data about the routes.

$result = array(
  "routes" => array(
    array("name" => "/home", 
      "templateUrl" => "partials/home.html", 
      "controller" => "HomeCtrl", 
      "isFree" => true),
    array("name" => "/contact", 
      "templateUrl" => "partials/contact.html", 
      "controller" => "ContactCtrl", 
      "isFree" => true),
    array("name" => "/about", 
      "templateUrl" => "partials/about.html", 
      "controller" => "AboutCtrl", 
      "isFree" => true)
  ),
  "default" => "/home"
);

$json = json_encode($result);
echo $json;

return;

Deferred Routes Definition

Normally routes are defined in the configuration block, because the $routeProvider can be used only there.

Unfortunately when the configuration block is executed all the most important services of AngularJS are still undefined; for this reason it’s not possible to use $http to query the server for the route list. So in the configuration block I just created a global reference to $routeProvider, which can be used practically everywhere to configure the routes of the web application.

'use strict';

var $routeProviderReference;
var app = angular.module('myApp', ['myApp.controllers']);

app.config(['$routeProvider', function($routeProvider) {
  $routeProviderReference = $routeProvider;
}]);

Now, to get the other routes the first step is to query the server to get the data.

This should be done as soon as possible; and for this reason the best solution is to use the run method to register a callback that is executed when the injector is done loading all modules.

app.run(['$rootScope', '$http', '$route', 
  function($rootScope, $http, $route) {

    $http.get('get-routes.php').success(function (data) {   

      var j = 0,
        currentRoute;

      var def = data.default;

      for ( ; j < data.routes.length; j++ ) {

        currentRoute = data.routes[j];

        $routeProviderReference.when(currentRoute.name, {
          templateUrl: currentRoute.templateUrl,
          controller: currentRoute.controller,
          isFree: currentRoute.isFree
        });

      }

      $routeProviderReference.otherwise({ redirectTo: def });

      $route.reload();

    });

  }]);

A word on possible applications

Last week I wrote a post about an approach to user authentication in AngularJS web app; and in this occasion I already wrote about how to mantain reserved the reserved content of a web application. Of course deferring the definition of the routes, and differentiating the available routes for a logged user, from those available to a simple visitor, can help to achieve this purpose.