3

I'm having a problem which I'm not sure whether is a down to a limitation of Angular (possibly) or a limitation of my knowledge of Angular (probably).

I am trying to take an array of controllers, and dynamically create/load them. I have a prototype working to the point where the controllers run and the root scope can be accessed, but I cannot dynamically attach ng-controller to divs in order to encapsulate the controllers into their own local scopes.

The problem is that the templates are bound to the root scope but not to their own scopes.

My example will hopefully explain my quandary better.

JSFiddle: http://jsfiddle.net/PT5BG/22/ (last update 16:30 BST)

It may not make sense why I am doing it this way, but I have pulled this concept out of a larger system I am creating. In case you have other suggestions, these are the laws by which I am bound:

  • Controllers cannot be hard-coded, they must be built from an array
  • Scopes cannot be shared between controllers, they must have their own scopes

The docs on AngularJS are not exactly comprehensive so I'm hoping someone here can help!

Adam K Dean
  • 7,387
  • 10
  • 47
  • 68
  • 2
    Typically you need to `$compile` the HTML element, but I can't get that working. I can get it working on a newly created element though (http://jsfiddle.net/PT5BG/23/). I wonder if it doesn't have something to do with the created scope inside of each ngRepeated element? – Langdon Oct 16 '13 at 16:07
  • It could be an issue to do with that. I thought `$compile` may come into it, and this could possibly work. Thanks @Langdon! – Adam K Dean Oct 16 '13 at 16:10
  • 2
    As another approach, you may be able to make use of `ngInclude`, which you can now nest inside of `ngRepeat` as of 1.2.0-rc.3. – Langdon Oct 16 '13 at 16:13
  • In the bigger system, I do use ngInclude, but looking at this, it may be a case of having to include it another way. As we're using 1.0.8 at the moment, the ngInclude may not work. Or it may be a case of upgrading to 1.2. Thanks again! – Adam K Dean Oct 16 '13 at 16:15
  • 2
    I'm sure it's possible... someone else should come along. It's interesting to note that the last Ctrl in the list wins... if you change `$scope.modules`, and put FooCtrl last, every ngRepeat will say Foo... I didn't think executing `$controller` would affect anything unless you applied it to something (with $compile). – Langdon Oct 16 '13 at 16:17
  • 1
    Also worth noting another approach: http://stackoverflow.com/questions/15250644/angularjs-loading-a-controller-dynamically - and the fiddle http://jsfiddle.net/MzseV/7/ – Daniel Dawes Oct 16 '13 at 16:26

2 Answers2

1

You can just pass the controller name through and use the $controller service and pass the locals through to it. You'll need some sort of ModuleCtrl thing to co-ordinate all this. Here is a basic example that does what you want.

http://jsfiddle.net/PT5BG/62/

angular.module('app', [])
.controller('AppCtrl', function ($scope, $controller) {
    $scope.modules = [
        { name: "Foo", controller: "FooCtrl" },
        { name: "Bar", controller: "BarCtrl" }]
})
.controller('ModuleCtrl', function ($scope, $rootScope, $controller) {
    $controller($scope.module.controller, { $rootScope: $rootScope, $scope: $scope });
})
.controller('FooCtrl', function ($rootScope, $scope) {
    $rootScope.rootMessage = "I am foo";
    $scope.localMessage = "I am foo";
    console.log("Foo here");
})
.controller('BarCtrl', function ($rootScope, $scope) {
    $rootScope.rootMessage = "I am bar";
    $scope.localMessage = "I am bar";
    console.log("Bar here");
});
Chris Nicola
  • 14,384
  • 6
  • 47
  • 61
  • Thanks, I did try something like this but for some reason, `$controller` was attaching the template file to the rootScope and not the child scope. I came up with a simpler solution, which I will post in a moment. Thanks though! – Adam K Dean Oct 20 '13 at 08:18
0

The way I finally got around this was quite simple, it was just a case of working it out.

So I have a list of modules, that I get from an API, and I want to instantiate them. I include the template file by building the path via convention, like so:

<!-- the ng-repeat part of the code -->
<div ng-repeat="module in modules">
    <ng-include src="module.name + '.tpl.html'"></ng-include>
</div>

In each of the modules template files, I then declare the ng-controller and I declare a method to fire in ng-init. As the template is still within the ng-repeat loop, it has access to module, which has the data we want to pass to the child controller. ng-init runs on the local scope, so we pass in the module object:

<!-- the template of the module -->
<div ng-controller="ModuleCtrl" ng-init="init(module)">
    ...
</div>

And then we store it on the local scope and there you go, injected the object.

/* the controller of the module */
.controller('ModuleCtrl', function ($scope) {
    $scope.init = function(module) {
        this.module = module;
    };

    // this.module is now available inside the controller
});

It took a bit of hacking but it works perfectly for now.

Adam K Dean
  • 7,387
  • 10
  • 47
  • 68