Using the AngularUI Router to dynamically load controllers

Problem

I'm using ui-router in an app that will have dozens of templates. Each template will have a controller.

From what I've been reading, something like this (to setup a route) ought to work:

.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('start', {
      url: '/start',
      templateUrl: 'partials/start.html',
      controller: 'StartCtrl'
    })
});

That's assuming StartCtrl was previously defined. The app will eventually have dozens of controllers, and don't want the overhead of downloading all of them at once. How can I load a controller only when the template is requested?

Problem courtesy of: mbursill

Solution

I use RequireJS for this. And an in a provider, say lazyProvider:

In your lazy provider...

this.resolve = function(controller){
    return { myCtrl: ['$q',function ($q){
            var defer = $q.defer();
            require(['/app/controllers/'+controller,'myApp'],function(ctrl,myApp){
                myApp.register.controller(controller,ctrl);
                defer.resolve();

            }
            return defer.promise;
        }]
    };
};

In your ui-router resolve: property do:

resolve: lazyProvider.resolve('myCtrl');

You'll need to expose provider registers on your app so you can register them later, like:

myApp.config(function($urlRouterProvider, $stateProvider,
          $controllerProvider, $compileProvider, $filterProvider,$provide){
    //If your myApp isn't already a module...
    define('myApp',[],function(){return myApp});
    myApp.register = {
        controller: $controllerProvider.register,
        directive: $compileProvider.directive,
        filter: $filterProvider.register,
        factory: $provide.factory,
        service: $provide.service,
        constant: $provide.constant
    }

And your controllers:

define(['/dependencies'],function(deps){
    function MyCtrl($scope){...}
    return MyCtrl;
}

This is essentially what Dan Wahlin has shared here

Solution courtesy of: calebboyd

Discussion

After playing around with @calebboyd sample, I got it working using System.js and ES6. What I liked about this implementation is that using ES6 modules, everything depends only on the file names which are already unique, so you don't need to care about controller names conflicting or even naming them. Controllers are anonymous.

This implementation assumes you have a folder "pages" with template/controller pairs like this:

/pages
  /page1.html
  /page1.js
  /page2.html
  /page2.js

Once you access /#pages/page1, it will load both the template and the controller dynamically.

Here is your "app.js":

import angular from 'angular';
import 'angular-ui-router';

angular.module('app', ['ui.router'])
  .config(($stateProvider, $urlRouterProvider, $controllerProvider) => {

    // save reference to the register method, so we can use inside the 'resolve'
    var registerController = $controllerProvider.register;

    // register a single state that takes the page name as a parameter
    $stateProvider
      .state('pages', {
        url: "/pages/:name",

        // the url and the controller name are dynamically created
        templateUrl: $stateParams => "pages/" + $stateParams.name + ".html",
        controllerProvider: $stateParams => $stateParams.name + '_DynamicController as vm',

        resolve: {

          'ctrl': ($stateParams, $q) => {

            var script = 'pages/' + $stateParams.name;
            var controllerName = $stateParams.name + '_DynamicController';

            // once System.js loads the module, we register using the
            // saved registerController function with the dynamic name
            return System.import(script)
              .then(ctrl => registerController(controllerName, ctrl['default']));
          }
        }
      });
  });

Here is a sample controller in "page1.js":

export default class {
  constructor() {
    this.data = "inside the controller";
  }
}

Sample template in "page1.html":

<h1>Page1 Template</h1>
Loaded: {{ vm.data }}
Discussion courtesy of: Natan

This recipe can be found in it's original form on Stack Over Flow.