AngularAMD + ui-router + dynamic controller name?

Solution 1:

This is a link to working plunker.

solution

We need two features of the UI-Router:

  • resolve (to load the missing pieces of js code)
  • controllerProvider (see cites from documentation below)

angularAMD - main.js definition

This would be our main.js, which contains smart conversion controllerName - controllerPath:

require.config({

    //baseUrl: "js/scripts",
    baseUrl: "",

    // alias libraries paths
    paths: {
        "angular": "angular",
        "ui-router": "angular-ui-router",
        "angularAMD": "angularAMD",

        "DefaultCtrl": "Controller_Default",
        "OtherCtrl": "Controller_Other",
    },

    shim: {
        "angularAMD": ["angular"],
        "ui-router": ["angular"],
    },

    deps: ['app']
});

controllers:

// Controller_Default.js
define(['app'], function (app) {
    app.controller('DefaultCtrl', function ($scope) {
        $scope.title = "from default"; 
    });
}); 

// Controller_Other.js
define(['app'], function (app) {
    app.controller('OtherCtrl', function ($scope) {
        $scope.title = "from other";
    });
});

app.js

Firstly we would need some method converting the param (e.g. id) into controller name. For our test purposes let's use this naive implementation:

var controllerNameByParams = function($stateParams)
{
    // naive example of dynamic controller name mining
    // from incoming state params

    var controller = "OtherCtrl";

    if ($stateParams.id === 1) {
        controller = "DefaultCtrl";
    }

    return controller;
}

.state()

And that would be finally our state definition

$stateProvider
    .state("default", angularAMD.route({
        url: "/{id:int}",
        templateProvider: function($stateParams)
        {
            if ($stateParams.id === 1)
            {
                return "<div>ONE - Hallo {{title}}</div>";
            }
            return "<div>TWO - Hallo {{title}}</div>";
        },
        resolve: {
            loadController: ['$q', '$stateParams',
                function ($q, $stateParams)
                {
                    // get the controller name === here as a path to Controller_Name.js
                    // which is set in main.js path {}
                    var controllerName = controllerNameByParams($stateParams);

                    var deferred = $q.defer();
                    require([controllerName], function () { deferred.resolve(); });
                    return deferred.promise;
                }]
        },
        controllerProvider: function ($stateParams)
        {
            // get the controller name === here as a dynamic controller Name
            var controllerName = controllerNameByParams($stateParams);
            return controllerName;
        },
    }));

Check it here, in this working example

documentation

As documented here: $stateProvider, for a state(name, stateConfig) we can use controller and controllerProvider. Some extract from documentation:

controllerProvider

...

controller (optional) stringfunction

Controller fn that should be associated with newly related scope or the name of a registered controller if passed as a string. Optionally, the ControllerAs may be declared here.

controller: "MyRegisteredController"

controller:
"MyRegisteredController as fooCtrl"}

controller: function($scope, MyService) {
$scope.data = MyService.getData(); }

controllerProvider (optional) function

Injectable provider function that returns the actual controller or string.

controllerProvider:
  function(MyResolveData) {
    if (MyResolveData.foo)
      return "FooCtrl"
    else if (MyResolveData.bar)
      return "BarCtrl";
    else return function($scope) {
      $scope.baz = "Qux";
    }
  }

...

resolve

resolve (optional) object

An optional map<string, function> of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them ALL to be resolved before the controller is instantiated...

I.e. let's use controllerProvider:

... to resolve the controller name dynamically...

In case, that you managed to get here, maybe you'd like to check another similar solution with RequireJS - angular-ui-router with requirejs, lazy loading of controller