controller

There are two ways in which we can define the controller for a particular route. This is an optional argument in the $routeProvider.when definition, in case we have not directly defined the controller in the HTML using the ng-controller directive. If the controller has already been declared using the angularApp.controller("MyCtrl") syntax, we can specify the name of the controller as a string. The controller key can also use the ng-controller’s controllerAs syntax, so we can use it like MyCtrl as ctrl in the controller key for the route definition.

The other option is to define the controller inline, in which case we pass the controller function directly to the controller key. We can also use the array syntax to inject our dependencies in a way that is uglification-safe. The code might look something like:

$routeProvider.when('/test',{template:'<h1>Test Route</h1>',controller:['$window',function($window){$window.alert('Test route has been loaded!');}]});

In this example, we define a route that has an inline controller with a dependency on the $window service. All the route does is show a window alert on load. It is recommended that we use the controller syntax to define our controllers; defining it inline will make it hard to write unit tests or reuse it in other routes.

controllerAs

The controllerAs key is there as a convenience in case we don’t want to define what the controller should be named inline in the controller key. The two route definitions in the following example are equivalent in terms of functionality:

$routeProvider.when('/test',{template:'<h1>Test Route</h1>',controller:'MyCtrl as ctrl'});$routeProvider.when('/test',{template:'<h1>Test Route</h1>',controller:'MyCtrl',controllerAs:'ctrl'});

Whether we use the controller key and define the renaming in it, or define it separately using the controllerAs key, there is no functional difference. It is purely a personal preference.

redirectTo

There are cases where some routes that used to exist have been renamed or cases where multiple URLs in the application are actually the same page underneath. In such cases, the redirectTo key can be used to specify the URL to which that particular route must navigate when it is encountered. It can be used for error handling and common route handling. For example:

$routeProvider.when('/new',{template:'<h1>New Route</h1>'});$routeProvider.when('/old',{redirectTo:'/new'});

In this example, angularjs opens the /new URL when the user enters either /#/new or /#/old in the browser.

resolve The final configuration, and most versatile and complex of the route configuration options, is the resolve. In the next section, we cover how to implement resolves. At a conceptual level, resolves are a way of executing and finishing asynchronous tasks before a particular route is loaded. This is a great way to check if the user is logged in and has authorization and permissions, and even preload some data before a controller and route are loaded into the view.

 

 

Using Resolves for Pre-Route Checks

As mentioned in the previous section, when we define a resolve, we can define a set of asynchronous tasks to execute before the route is loaded. A resolve is a set of keys and functions. Each function can return a value or a promise. A sample resolve, which makes a server call and returns a hardcoded value, is shown here:

angular.module('resolveApp',['ngRoute']).value('Constant',{MAGIC_NUMBER:42}).config(['$routeProvider',function($routeProvider){$routeProvider.when('/',{template:'<h1>Main Page, no resolves</h1>'}).when('/protected',{template:'<h2>Protected Page</h2>',resolve:{immediate:['Constant',function(Constant){returnConstant.MAGIC_NUMBER*4;}],async:['$http',function($http){return$http.get('/api/hasAccess');}]},controller:['$log','immediate','async',function($log,immediate,async){$log.log('Immediate is ',immediate);$log.log('Server returned for async',async);}]});}]);

This example expects that there is a server-side API available at /api/hasAccess, which on a GET request returns a status 200 response if the user has access, and a nonstatus 200 response if the user does not have access.

There are two routes in this example. The first route is a very standard route that loads an HTML template when the route definition is encountered. We have no resolves on this route, so it always loads properly.

The second definition contains a resolve defined with two keys, immediate and async. Note that these are keys of our own choosing, so this could very well be myKey1 and myKey2. angularjs does not expect or force us to use any particular key. Each key takes an array, which is the angularjs Dependency Injection syntax. We define the dependencies for the resolve in the array, and get it injected into the resolve function. The first resolve key, immediate, gets the Constant dependency injected into it, and returns a constant value multiplied by some number. The second resolve key, async, gets the $http dependency injected into it, and makes a server call to /api/hasAccess. It then returns the promise for that particular server call. angularjs guarantees the following:

  • If the resolve function returns a value, angularjs immediately finishes executing and treats it as a successful resolve.
  • If the resolve function returns a promise, angularjs waits for the promise to return and treats the resolve as successful if the promise is successful. If the promise is rejected, the resolve is treated as a failure.
  • Because of the resolve function, angularjs ensures that the route does not load until all the resolve functions are finished executing. If there are multiple resolve keys that make asynchronous calls, angularjs executes all of them in parallel and waits for all of them to finish executing before loading the page.
  • If any of the resolves encounter an error or any of the promises returned are rejected (is a failure), angularjs doesn’t load the route.

In the previous example, because the immediate resolve key is returning only a value, it is treated as a successful resolve every time. The async resolve key makes a server call, and if it is successful, the route is loaded. If the server returns a non-200 status response, angularjs doesn’t load the page. angularjs still loads and caches the template if any of the resolves fail, but the controller associated with the route isn’t loaded and the HTML doesn’t make it into the ng-view.

In these cases, the user still sees the last page he was on. So it might not be a great user experience, because the user won’t know that something went wrong. In

One other interesting thing about resolves is that we can get the value from each of the resolve keys injected into our controller, if we want or need the data. Each key can directly be injected into the controller by adding it as a dependency. This is over and above any angularjs service dependency we might have. The following items are injected into the controller:

  • The value itself, if the resolve function was returning a value
  • The resolution of a promise, if the resolve function was returning a promise

In the case of the async resolve, we get the resolved value of the promise, which is the response object from the server, with the config, status, headers, and data. This is what’s normally passed to the success function of the then of the promise:

$http.get('/api/hasAccess').then(function(response){console.log('I am passed to the controller',response);returnresponse;});

In this case, response is the value that async will take when it is injected into the controller.

  • Enable HTML5 mode as part of the application config on the client side as follows:

    angular.module('myHtml5App',['ngRoute']).config(['$locationProvider','$routeProvider',function($locationProvider,$routeProvider){$locationProvider.html5Mode(true);//Optional$locationProvider.hashPrefix('!');// Route configuration here as normal// Route for /first/page// Route for /second/page}]);
  • To set HTML5 mode, we ask for $locationProvider as part of the configuration, and call the function html5Mode with true on it. It’s recommended that we also set the hashPrefix as ! to easily support SEO, as we will see in the next section. This is all we need to do on the client side for nonhash URLs in angularjs.

  • In index.html, we need to add the <base> tag with an href attribute to the <head> portion. This is to tell the browser where, in relation to the URL, the static resources are served from, so that if the application requests an image or CSS file with a relative path, it doesn’t take it from the current URL necessarily.

    For example, let us say our base application is served from http://www.mywebsite.com/app, and it has HTML5 mode enabled. So when a user navigates to http://www.mywebsite.com/app/route/15, our server still serves the index.html page, but for the browser, the application path is /app/route/15. So all relative files will be relative to this URL. Instead, we need something like the following in our HTML:

    <html><head><basehref="/app"/></head></html>

    This would ensure that regardless of the URL, all relative paths would be resolved relative to /app and not to some other URL. If your application is being served from /, then have <base href="/"> in your <head> tag.

  • On the server side, we need a rule that states that when the server sees a request for /first/page or /second/page, it needs to serve the content that it normally serves for the / request, which is usually index.html. In NodeJS, it might look something like this:

    varexpress=require('express'),url=require('url');varapp=express();// express configuration herevarINDEX_HTML=fs.readFileSync(__dirname+'/index.html','utf-8');varACCEPTABLE_URLS=['/first/page','/second/page'];app.use(function(req,res,next){varparts=url.parse(req.url);for(vari=0;i<ACCEPTABLE_URLS.length;i++){if(parts.pathname.indexOf(ACCEPTABLE_URLS[i])===0){// We found a match to one of our// client-side routesreturnres.send(200,INDEX_HTML);}}returnnext();});// Other routes here

    We have a simple node server that reads and caches the contents of index.html into the INDEX_HTML variable. After that, if it sees a request for /first/page or /second/page, it returns the contents of INDEX_HTML; otherwise, it continues to the correct response handler by calling next()