To dynamically set a controller in a template, it helps to have a reference to the constructor function associated to a controller. The constructor function for a controller is the function you pass in to the controller()
method of Angular's module API.
Having this helps because if the string passed to the ngController
directive is not the name of a registered controller, then ngController
treats the string as an expression to be evaluated on the current scope. This scope expression needs to evaluate to a controller constructor.
For example, say Angular encounters the following in a template:
ng-controller="myController"
If no controller with the name myController
is registered, then Angular will look at $scope.myController
in the current containing controller. If this key exists in the scope and the corresponding value is a controller constructor, then the controller will be used.
This is mentioned in the ngController
documentation in its description of the parameter value: "Name of a globally accessible constructor function or an expression that on the current scope evaluates to a constructor function." Code comments in the Angular source code spell this out in more detail here in src/ng/controller.js
.
By default, Angular does not make it easy to access the constructor associated to a controller. This is because when you register a controller using the controller()
method of Angular's module API, it hides the constructor you pass it in a private variable. You can see this here in the $ControllerProvider source code. (The controllers
variable in this code is a variable private to $ControllerProvider
.)
My solution to this issue is to create a generic helper service called registerController
for registering controllers. This service exposes both the controller and the controller constructor when registering a controller. This allows the controller to be used both in the normal fashion and dynamically.
Here is code I wrote for a registerController
service that does this:
var appServices = angular.module('app.services', []);
// Define a registerController service that creates a new controller
// in the usual way. In addition, the service registers the
// controller's constructor as a service. This allows the controller
// to be set dynamically within a template.
appServices.config(['$controllerProvider', '$injector', '$provide',
function ($controllerProvider, $injector, $provide) {
$provide.factory('registerController',
function registerControllerFactory() {
// Params:
// constructor: controller constructor function, optionally
// in the annotated array form.
return function registerController(name, constructor) {
// Register the controller constructor as a service.
$provide.factory(name + 'Factory', function () {
return constructor;
});
// Register the controller itself.
$controllerProvider.register(name, constructor);
};
});
}]);
Here is an example of using the service to register a controller:
appServices.run(['registerController',
function (registerController) {
registerController('testCtrl', ['$scope',
function testCtrl($scope) {
$scope.foo = 'bar';
}]);
}]);
The code above registers the controller under the name testCtrl
, and it also exposes the controller's constructor as a service called testCtrlFactory
.
Now you can use the controller in a template either in the usual fashion--
ng-controller="testCtrl"
or dynamically--
ng-controller="templateController"
For the latter to work, you must have the following in your current scope:
$scope.templateController = testCtrlFactory