Sunday, November 9, 2014

Dynamically Loading Controllers and Views with AngularJS/$controllerProvider and RequireJS

Copyright from: weblogs.asp.net/dwahlin

A complete sample application that uses the techniques shown in this post can be found athttps://github.com/DanWahlin/CustomerManager.
AngularJS provides a simple way to associate a view with a controller and load everything at runtime using the$routeProvider object. Routing code is typically put in a module’s config() function and looks similar to the following:
$routeProvider
     .when('/customers',
        {
            controller: 'CustomersController',
            templateUrl: '/app/views/customers.html'
        })
    .when('/customerorders/:customerID',
        {
            controller: 'CustomerOrdersController',
            templateUrl: '/app/views/customerOrders.html'
        })
    .when('/orders',
        {
            controller: 'OrdersController',
            templateUrl: '/app/views/orders.html'
        })
    .otherwise({ redirectTo: '/customers' });

While this type of code works great for defining routes it requires controller scripts to be loaded upfront in the main shell page by default. That works fine in some scenarios but what if you have a lot of controller scripts and views in a given application and want to dynamically load them on-the-fly at runtime? One way of dealing with that scenario is to define a resolve property on each route and assign it a function that returns a promise. The function can handle dynamically loading the script containing the target controller and resolve the promise once the load is complete. An example of using the resolve property is shown next:

$routeProvider
    .when('/customers',
        {
            templateUrl: '/app/views/customers.html',
            resolve: resolveController('/app/controllers/customersController.js')
        });

This approach works well in cases where you don’t want all of your controller scripts loaded upfront, but it still doesn’t feel quite right – at least to me. I personally don’t like having to define two paths especially if you’ll be working with a lot of routes. If you’ve ever worked with a framework that uses convention over configuration then you’ll know that we can clean up this code by coming up with a standard convention for naming views and controllers. Coming up with a convention can help simplify routes and maintenance of the application over time. The approach that I’ll demonstrate in this post uses the following routing code to define the path, view and controller: 

$routeProvider
    .when('/customers', route.resolve('Customers'))
    .when('/customerorders/:customerID', route.resolve('CustomerOrders'))
    .when('/orders', route.resolve('Orders'))
    .otherwise({ redirectTo: '/customers' });


Notice that a single value is passed into the route.resolve() function. Behind the scenes the function will automatically create the path to the view and the path to the controller based on some simple conventions and then load the appropriate files dynamically. You can access a sample (work in progress) project athttps://github.com/DanWahlin/CustomerManager. Let’s take a look at how it works. 

Dynamically Loading Controllers

The following diagram shows the different players involved in simplifying routes and dynamically loading controllers. RequireJS is used to dynamically load controller JavaScript files and make an application’s main module available to the controllers so that they’re registered properly after they’re loaded. 
image

Here’s how it works:
  1. A file named main.js defines custom scripts that will be loaded using RequireJS. I originally defined 3rd party libraries such as AngularJS in main.js as well but decided there simply wasn’t enough benefit over loading them at the bottom of the initial page using a

No comments: