30

I want to dynamically specify a controller based on a config that we load. Something like this:

<div ng-controller="{{config.controllerNameString}}>
    ...
</div>

How do I do this in angular? I thought this would be very easy, but I can seem to find a way of doing this.

RonSper
  • 693
  • 1
  • 7
  • 19

3 Answers3

34

What you want to do is have another directive run before anything else is called, get the controller name from some model remove the new directive and add the ng-controller directive, then re-compile the element.

That looks like this:

global.directive('dynamicCtrl', ['$compile', '$parse',function($compile, $parse) {
  return {
    restrict: 'A',
    terminal: true,
    priority: 100000,
    link: function(scope, elem) {
      var name = $parse(elem.attr('dynamic-ctrl'))(scope);
      elem.removeAttr('dynamic-ctrl');
      elem.attr('ng-controller', name);
      $compile(elem)(scope);
    }
  };
}]);

Then you could use it in your template, like so:

<div dynamic-ctrl="'blankCtrl'">{{tyler}}</div>

with a controller like this:

global.controller('blankCtrl',['$scope',function(tyler){
    tyler.tyler = 'tyler';
    tyler.tyler = 'chameleon';
}]);

There's probably a way of interpolating the value ($interpolate) of the dynamic-ctrl instead of parsing it ($parse), but I couldn't get it to work for some reason.

RonSper
  • 693
  • 1
  • 7
  • 19
Kevin Beal
  • 10,500
  • 12
  • 66
  • 92
  • Thank you. Exactly what I was looking for. In my case I wanted to be passing a $scope variable instead of a string. So I just needed to do dynamic-ctrl="myScopeVariable" – mlhuff12 Jun 11 '16 at 22:28
  • 7
    Nide solution. However, I suggest a priority of 550 instead of 100000. This way, the directive is executed before ng-controller (priority 600) but AFTER ng-if (priority 600). Otherwise you controller get initialized (during the call to $compile(elem)(scope)) even if the element is not on the page (if ng-if is false). Also, you can access to element's attributes using 'attrs' parameter of link function to handle camel casing (ie: attrs.dynamicCtrl). – Marc Oct 02 '16 at 14:11
5

I'm using it in ng-repeat, so this is improved code for loops and sub objects:

Template:

<div class="col-xs6 col-sm-5 col-md-4 col-lg-3" ng-repeat="box in boxes">
<div ng-include src="'/assets/js/view/box_campaign.html'" ng-dynamic-controller="box.type"></div>
</div>

Directive:

mainApp.directive('ngDynamicController', ['$compile', '$parse',function($compile, $parse) {
  return {
      scope: {
          name: '=ngDynamicController'
      },
      restrict: 'A',
      terminal: true,
      priority: 100000,
      link: function(scope, elem, attrs) {
          elem.attr('ng-controller', scope.name);
          elem.removeAttr('ng-dynamic-controller');

          $compile(elem)(scope);
      }
  };
}]);
numaroth
  • 1,295
  • 4
  • 25
  • 36
ido niger
  • 86
  • 2
  • 6
3

Personally the 2 current solutions here didn't work for me, as the name of the controller would not be known when first compiling the element but later on during another digest cycle. Therefore I ended up using:

myapp.directive('dynamicController', ['$controller', function($controller) {
  return {
    restrict: 'A',
    scope: true,
    link: function(scope, elem, attrs) {
      attrs.$observe('dynamicController', function(name) {
        if (name) {
          elem.data('$Controller', $controller(name, {
            $scope: scope,
            $element: elem,
            $attrs: attrs
          }));
        }
      });
    }
  };
}]);
Guillaume
  • 2,912
  • 3
  • 35
  • 59