Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading an AngularJS controller dynamically

I have an existing page into which I need to drop an angular app with controllers that can be loaded dynamically.

Here's a snippet which implements my best guess as to how it should be done based on the API and some related questions I've found:

// Make module Foo angular.module('Foo', []); // Bootstrap Foo var injector = angular.bootstrap($('body'), ['Foo']); // Make controller Ctrl in module Foo angular.module('Foo').controller('Ctrl', function() { }); // Load an element that uses controller Ctrl var ctrl = $('<div ng-controller="Ctrl">').appendTo('body'); // compile the new element injector.invoke(function($compile, $rootScope) {     // the linker here throws the exception     $compile(ctrl)($rootScope); }); 

JSFiddle. Note that this is a simplification of the actual chain of events, there are various async calls and user inputs between the lines above.

When I try to run the above code, the linker which is returned by $compile throws: Argument 'Ctrl' is not a function, got undefined. If I understood bootstrap correctly, the injector it returns should know about the Foo module, right?

If instead I make a new injector using angular.injector(['ng', 'Foo']), it seems to work but it creates a new $rootScope which is no longer the same scope as the element where the Foo module was bootstrapped.

Am I using the right functionality to do this or is there something I've missed? I know this isn't doing it the Angular way, but I need to add new components that use Angular to old pages that don't, and I don't know all the components that might be needed when I bootstrap the module.

UPDATE:

I've updated the fiddle to show that I need to be able to add multiple controllers to the page at undetermined points in time.

like image 216
Jussi Kosunen Avatar asked Mar 06 '13 14:03

Jussi Kosunen


People also ask

Can you create controller in AngularJS?

AngularJS application mainly relies on controllers to control the flow of data in the application. A controller is defined using ng-controller directive. A controller is a JavaScript object that contains attributes/properties, and functions.

What is controller in AngularJS?

AngularJS controllers are used to control the flow of data of AngularJS application. A controller is defined using ng-controller directive. A controller is a JavaScript object containing attributes/properties and functions.


2 Answers

I've found a possible solution where I don't need to know about the controller before bootstrapping:

// Make module Foo and store $controllerProvider in a global var controllerProvider = null; angular.module('Foo', [], function($controllerProvider) {     controllerProvider = $controllerProvider; }); // Bootstrap Foo angular.bootstrap($('body'), ['Foo']);  // .. time passes ..  // Load javascript file with Ctrl controller angular.module('Foo').controller('Ctrl', function($scope, $rootScope) {     $scope.msg = "It works! rootScope is " + $rootScope.$id +         ", should be " + $('body').scope().$id; }); // Load html file with content that uses Ctrl controller $('<div id="ctrl" ng-controller="Ctrl" ng-bind="msg">').appendTo('body');  // Register Ctrl controller manually // If you can reference the controller function directly, just run: // $controllerProvider.register(controllerName, controllerFunction); // Note: I haven't found a way to get $controllerProvider at this stage //    so I keep a reference from when I ran my module config function registerController(moduleName, controllerName) {     // Here I cannot get the controller function directly so I     // need to loop through the module's _invokeQueue to get it     var queue = angular.module(moduleName)._invokeQueue;     for(var i=0;i<queue.length;i++) {         var call = queue[i];         if(call[0] == "$controllerProvider" &&            call[1] == "register" &&            call[2][0] == controllerName) {             controllerProvider.register(controllerName, call[2][1]);         }     } } registerController("Foo", "Ctrl"); // compile the new element $('body').injector().invoke(function($compile, $rootScope) {     $compile($('#ctrl'))($rootScope);     $rootScope.$apply(); }); 

Fiddle. Only problem is that you need to store the $controllerProvider and use it in a place where it really shouldn't be used (after the bootstrap). Also there doesn't seem to be an easy way to get at a function used to define a controller until it is registered, so I need to loop through the module's _invokeQueue, which is undocumented.

UPDATE: To register directives and services, instead of $controllerProvider.register simply use $compileProvider.directive and $provide.factory respectively. Again, you'll need to save references to these in your initial module config.

UDPATE 2: Here's a fiddle which automatically registers all controllers/directives/services loaded without having to specify them individually.

like image 93
Jussi Kosunen Avatar answered Sep 22 '22 12:09

Jussi Kosunen


bootstrap() will call the AngularJS compiler for you, just like ng-app.

// Make module Foo angular.module('Foo', []); // Make controller Ctrl in module Foo angular.module('Foo').controller('Ctrl', function($scope) {      $scope.name = 'DeathCarrot' }); // Load an element that uses controller Ctrl $('<div ng-controller="Ctrl">{{name}}</div>').appendTo('body'); // Bootstrap with Foo angular.bootstrap($('body'), ['Foo']); 

Fiddle.

like image 39
Mark Rajcok Avatar answered Sep 19 '22 12:09

Mark Rajcok