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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With