Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically Loading Controllers and ng-include

Tags:

angularjs

At the moment I have an app that has a sidebar, and the sidebar loads different html templates using ng-include based on what operation the user chooses to do. It's a map related app, so for example, if the user selects the 'Add Leg' button it will load the add_leg.html template into the sidebar using ng-include:

// The Javascript code:
$scope.addLeg = function() {
    $scope.sidebar = true;
    $scope.sidebar_template = '/partials/legs/add_leg.html';    
}

// Simplified example of HTML:
<html>
<div ng-app="MyApp" ng-controller="MainCtrl">
    <a ng-click="addLeg()">Add Leg</a>
    <a ng-click="addRoute()">Add Route</a>
    <a ng-click="editLeg()">Edit Leg</a>
    <a ng-click="editRoute()">Edit Route</a>
    ...

    <div id="sidebar" ng-class="{'sidebar-hidden': sidebar == false}">
        <div ng-include src="sidebar_template"></div>
    </div>

    <google-map></google-map>
</div>

This is all well and good and works as desired, but at the moment my app is only using one controller (MainCtrl in js/controllers.js) and it's starting to get really cluttered. I've got a lot of methods now because the apps functionality is expanding. I'd like to split my controller up into multiple controllers whilst retaining this sidebar functionality.

I want to have MainCtrl as the main controller that controls the loading of the sidebar template (by changing the sidebar_template variable that points to the file destination), and I want it to control some of the global map related methods (like fetching suburb names from coordinates, etc).

I've tried to split it like so:

controllers/js/main.js - MainCtrl
controllers/js/legs.js - LegCtrl
controllers/js/routes.js - RouteCtrl

I want the LegCtrl and RouteCtrl to inherit the MainCtrl so I can access its scope and methods, that's all fine. But now the problem is how do I dynamically load the controller onto the sidebar div based on what functionality is required. Originally all of my methods were in MainCtrl, and that's on the wrapper div that surrounds the sidebar div (see above again for an example), so it wasn't a problem.

For example, say I press the 'Add Leg' button, it's going to need to call addLeg in LegCtrl, but LegCtrl isn't loaded on the app, so it doesn't have access to the method. I could keep addLeg() inside the MainCtrl, and have it change the sidebar_template variable to load the template, but nothing in the template will work because it is calling methods from inside the LegCtrl now.

I need to somehow dynamically load the controller on the sidebar's ng-include div, something like this perhaps:

// MainCtrl
$scope.addLeg = function() {
    $scope.required_controller = LegCtrl;

    $scope.sidebar = true;
    $scope.sidebar_template = '/partials/legs/add_leg.html';    

    LegCtrl.addLeg();
}

<div id="sidebar" ng-class="{'sidebar-hidden': sidebar == false}">
    <div ng-include src="sidebar_template" ng-controller="{{required_controller}}"></div>
</div>

In the non-working example above you can see a possible solution I've thought of, but I need LegCtrlto be the actual controller function, not an object (for ng-controller to work). I also need some way to call addLeg on the LegCtrl from the MainCtrl.addLeg (perhaps using broadcast?).

If anyone can point me in the right direction that'd be great. Sorry for the huge post, it needed a bit of explaining to make it coherent. Hopefully it makes sense. Thanks.

Update: I think I've found a solution using a service to act as the navigation control (it will load the relevant templates and broadcast an event to the new controller being dynamically loaded to tell it what function to execute):

http://plnkr.co/edit/Tjyn1OiVvToNntPBoH58?p=preview

Just trying to test this idea out now, but the broadcast .on doesn't work. I think it's because the broadcast fires before the template loads. How can I fix this? Is this a good solution for what I'm doing?

like image 789
andro1d Avatar asked Jul 23 '13 05:07

andro1d


3 Answers

If i have understood you correctly what you can try would be to create a template view specifically to create a new leg.

From the main controller implement some logic to show the template

$scope.addLeg=function() {
   $scope.showAddLeg=true;
}

The AddLeg template view would load the controller and hence provide a mechanism to actually add new leg. The template would look like

<script type="text/ng-template" class="template" id="addLegTemplate">
    <div ng-controller='LegsController'>
    <!--html for adding a new leg-->
    </div>
</script>

You can include this template inside you main html using ng-if + ng-include.

<div ng-if='showAddLeg'><div ng-include='addLegTemplate'/></div>

Basically you can create multiple view and bind to same controller (but instance would differ). In this case the LegsController can be binded to multiple views to support the complete functionality.

like image 72
Chandermani Avatar answered Nov 12 '22 03:11

Chandermani


I like this data driven scenario, just manipulate the rows in the templates:

$scope.templates = [
    { name: 'include1.html', url: 'partials/include1.html' }
];
$scope.addTemplate = function(name, url) {
    $scope.templates.push({ name: name, url: url });
};

and the markup:

<div ng-repeat="template in templates" ng-include="template.url"></div>

To add or remove views, just modify the templates et voila! If you need more controller code, then you can include a link to the script in the include. I guess there may be complications with binding to data in the partial view itself, but $compile should resolve those.

like image 5
Craig Avatar answered Nov 12 '22 04:11

Craig


I've forked your plunkr demo into one that I believe does what you're looking for: http://plnkr.co/edit/whsjBT?p=preview

This demonstrates an event being broadcast from one controller to another AFTER the 2nd controller (LegCtrl in our example here) is loaded via ng-include and passing it data.

I used $includeContentLoaded event from ng-include to delay broadcasting the event until angular reports that add_leg.html is loaded. Also I think there were some issues with how you were using $broadcast()... it's first parameter should be the same as the one used in $on() in LegCtrl

Another idea to consider is simply using your Navigation service itself to share state between the controllers if that's appropriate in your scenario.

like image 4
jandersen Avatar answered Nov 12 '22 05:11

jandersen