dynamic加载AngularJS控制器

我有一个现有的页面,我需要删除angular度的应用程序与控制器,可以dynamic加载。

这里有一个片段,它根据API和我发现的一些相关问题来实现我最好的猜测:

// 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 。 请注意,这是对事件链的简化,上面各行之间有各种asynchronous调用和用户input。

当我尝试运行上面的代码时,由$ compile throws返回的链接器: Argument 'Ctrl' is not a function, got undefined 。 如果我正确理解bootstrap,它返回的注入器应该知道Foo模块,对不对?

如果我使用angular.injector(['ng', 'Foo'])来创build一个新的注入器,它似乎可以工作,但它会创build一个新的$rootScope ,它不再与Foo模块被引导的元素。

我是否使用正确的function来做到这一点,或者是有什么我错过了? 我知道这不是以Angular的方式,但是我需要添加新的组件,使用Angular到旧的页面,而我不知道当我引导模块时可能需要的所有组件。

更新:

我已经更新了小提琴,以表明我需要能够添加多个控制器在不确定的时间点的网页。

我已经find了一个可能的解决scheme,在引导之前我不需要知道控制器。

 // 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(); }); 

小提琴 。 唯一的问题是你需要存储$controllerProvider并将其用在一个真正不应该使用的地方(在引导之后)。 另外,似乎没有一种简单的方法来获取用于定义控制器的函数,直到它被注册为止,所以我需要遍历模块的_invokeQueue ,这是没有logging的。

更新:注册指令和服务,而不是$controllerProvider.register只需使用$compileProvider.directive$provide.factory $compileProvider.directive分别。 同样,您需要在初始模块configuration中保存对这些参考的引用。

UDPATE 2: 这是一个自动注册所有控制器/指令/服务的小提琴 ,无需单独指定它们。

bootstrap()会像ng-app一样为你调用AngularJS编译器。

 // 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']); 

小提琴 。

我build议看一下ocLazyLoad库 ,该库在运行时注册模块(或现有模块上的控制器,服务等),并使用requireJs或其他此类库加载它们。

我刚刚改进了Jussi-Kosunen编写的function,以便所有的东西都可以通过一次调用完成。

 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(); }); } 

这样,你可以从任何地方加载你的模板,并以编程方式instanciate控制器,甚至嵌套。

下面是一个在另一个加载控制器的工作示例: http : //plnkr.co/edit/x3G38bi7iqtXKSDE09pN

我还需要添加多个视图,并将它们绑定到控制器在运行时从angularJs上下文外部的javascript函数,所以这就是我想出的:

 <div id="mController" ng-controller="mainController"> </div> <div id="ee"> 2nd controller's view should be rendred here </div> 

现在调用setCnt()函数将注入并编译html,并将其链接到第二个控制器:

 var app = angular.module('app', []); function setCnt() { // Injecting the view's html var e1 = angular.element(document.getElementById("ee")); e1.html('<div ng-controller="ctl2">my name: {{name}}</div>'); // Compile controller 2 html var mController = angular.element(document.getElementById("mController")); mController.scope().activateView(e1); } app.controller("mainController", function($scope, $compile) { $scope.name = "this is name 1"; $scope.activateView = function(ele) { $compile(ele.contents())($scope); $scope.$apply(); }; }); app.controller("ctl2", function($scope) { $scope.name = "this is name 2"; }); 

这里是一个例子来testing这个: http : //refork.com/x4bc

希望这可以帮助。

为什么不使用config和ui-router?

它是在运行时加载的,你不需要在html代码中显示你的控制器

比如像下面这样的东西

 var config = { config: function(){ mainApp.config(function ($stateProvider, $urlRouterProvider){ $urlRouterProvider.otherwise("/"); $stateProvider .state('index',{ views:{ 'main':{ controller: 'PublicController', templateUrl: 'templates/public-index.html' } } }) .state('public',{ url: '/', parent: 'index', views: { 'logo' : {templateUrl:'modules/header/views/logo.html'}, 'title':{ controller: 'HeaderController', templateUrl: 'modules/header/views/title.html' }, 'topmenu': { controller: 'TopMenuController', templateUrl: 'modules/header/views/topmenu.html' }, 'apartments': { controller: 'FreeAptController', templateUrl:'modules/free_apt/views/apartments.html' }, 'appointments': { controller: 'AppointmentsController', templateUrl:'modules/appointments/views/frm_appointments.html' }, } }) .state('inside',{ views:{ 'main':{ controller: 'InsideController', templateUrl: 'templates/inside-index.html' }, }, resolve: { factory:checkRouting } }) .state('logged', { url:'/inside', parent: 'inside', views:{ 'logo': {templateUrl: 'modules/inside/views/logo.html'}, 'title':{templateUrl:'modules/inside/views/title.html'}, 'topmenu': { // controller: 'InsideTopMenuController', templateUrl: 'modules/inside/views/topmenu.html' }, 'messages': { controller: 'MessagesController', templateUrl: 'modules/inside/modules/messages/views/initial-view-messages.html' }, 'requests': { //controller: 'RequestsController', //templateUrl: 'modules/inside/modules/requests/views/initial-view-requests.html' }, } }) }); }, }; 
 'use strict'; var mainApp = angular.module('mainApp', [ 'ui.router', 'ui.bootstrap', 'ui.grid', 'ui.grid.edit', 'ngAnimate', 'headerModule', 'galleryModule', 'appointmentsModule', ]); (function(){ var App = { setControllers: mainApp.controller(controllers), config: config.config(), factories: { authFactory: factories.auth(), signupFactory: factories.signup(), someRequestFactory: factories.saveSomeRequest(), }, controllers: { LoginController: controllers.userLogin(), SignupController: controllers.signup(), WhateverController: controllers.doWhatever(), }, directives: { signup: directives.signup(), // add new user openLogin: directives.openLogin(), // opens login window closeModal: directives.modalClose(), // close modal window ngFileSelect: directives.fileSelect(), ngFileDropAvailable: directives.fileDropAvailable(), ngFileDrop: directives.fileDrop() }, services: { $upload: services.uploadFiles(), } }; })(); 

上面的代码只是一个例子。

这样你就不需要在页面的任何地方放ng-controller="someController" – 你只需声明<body ng-app="mainApp">

模块内部的每个模块或模块可以使用相同的结构

这就是我所做的,真正的两个部分,使用ng-controller及其范围定义函数,然后使用$ controller服务来创builddynamic控制器:

首先,HTML – 我们需要一个静态控制器,它将实例化一个dynamic控制器。

 <div ng-controller='staticCtrl'> <div ng-controller='dynamicCtrl'> {{ dynamicStuff }} </div> </div> 

静态控制器'staticCtrl'定义了一个名为'dynamicCtrl'的作用域成员,它被调用来创builddynamic控制器。 ng控制器将按名称使用预定义的控制器,或者在当前的作用域中查找同名的函数。

 .controller('staticCtrl', ['$scope', '$controller', function($scope, $controller) { $scope.dynamicCtrl = function() { var fn = eval('(function ($scope, $rootScope) { alert("I am dynamic, my $scope.$id = " + $scope.$id + ", $rootScope.$id = " + $rootScope.$id); })'); return $controller(fn, { $scope: $scope.$new() }).constructor; } }]) 

我们使用eval()来接受一个string(我们的dynamic代码可以来自任何地方),然后是$ controller服务,它将使用预定义的控制器名称(正常情况下)或函数构造函数,接着是构造函数参数新的范围) – angular将注入(像任何控制器)的function,我们正在请求$ scope和$ rootScope上面。