Loading an AngularJS controller dynamically

2019-01-01 05:04发布

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.

8条回答
妖精总统
2楼-- · 2019-01-01 05:59

I have just improved the function written by Jussi-Kosunen so that all stuff can be done with one single call.

function registerController(moduleName, controllerName, template, container) {
    // Load html file with content that uses Ctrl controller
    $(template).appendTo(container);
    // 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]);
            }
        }

        angular.injector(['ng', 'Foo']).invoke(function($compile, $rootScope) {
            $compile($('#ctrl'+controllerName))($rootScope);
            $rootScope.$apply();
        });
}

This way you could load your template from anywhere and instanciate controllers programmatically, even nested.

Here is a working example loading a controller inside another one: http://plnkr.co/edit/x3G38bi7iqtXKSDE09pN

查看更多
旧人旧事旧时光
3楼-- · 2019-01-01 06:00

I would suggest to take a look at ocLazyLoad library, which registers modules (or controllers, services etc on existing module) at run time and also loads them using requireJs or other such library.

查看更多
登录 后发表回答