2013-03-06 140 views
61

我有一个现有页进我需要可动态加载控制器下降的角应用程序。加载一个AngularJS控制器动态

这里是它实现了我最好的猜测,应该如何基于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。请注意,这是对事件链的简化,上面各行之间有各种异步调用和用户输入。

当我试图运行上面的代码,它是由$编译返回的连接抛出:Argument 'Ctrl' is not a function, got undefined。如果我正确理解引导程序,它返回的喷油器应该知道Foo模块,对吧?

相反,如果我让使用angular.injector(['ng', 'Foo'])一个新的喷油器,它似乎工作,但它会创建一个新的$rootScope这不再是相同的范围内,其中Foo模块进行自举的元素。

我使用正确的功能来做到这一点或者是有什么我已经错过了?我知道这是不是这样做的角的方式,但我需要补充的是使用角度来不老的网页新的组件,而我不知道这一切时,我引导模块,可能需要的组件。

UPDATE:

我已经更新了fiddle表明,我需要能够在不确定个时刻多个控制器添加到页面。

+0

为什么你不只是声明所有的控制器的正常角度的方式在前面,然后做你的页面加载基于你在什么网页中插入编译? – boxed 2013-03-06 15:57:28

+1

有很多可能的控制器,我只打算在该特定页面上使用一个或两个控制器,但我不知道用户选择哪一个或两个控制器。 – 2013-03-07 06:51:24

+0

将所有这些控制器放入一个大(最好是缩小的).js文件中,并确保该文件被浏览器正确缓存。你不可能有这么多的控​​制器,这是一个问题。 – boxed 2013-03-07 08:20:13

回答

69

我已经找到了一个可能的解决方案,我并不需要引导之前了解的控制器:

// 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。唯一的问题是你需要存储$controllerProvider并将它用在真正不应该使用的地方(启动后)。也有似乎没有一种简单的方法来获得在使用,直到其注册定义控制器的功能,所以我通过模块_invokeQueue,这是无证需要循环。

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

UDPATE 2:Here's a fiddle它自动注册所有加载的控制器/指令/服务,而不必单独指定它们。

+1

在Update 2中的小提琴是完美的,我需要的。 – Tyson 2013-09-17 07:04:04

+0

嗯......当试图调用'.injector()'函数时,它给了我一个错误,将其称为未定义。我正在使用angular1.2.23。它在新版本中已被弃用吗? – Aarmora 2015-01-08 02:02:11

+0

它也给我错误! [错误:[NG:AREQ] http://errors.angularjs.org/1.2.26/ng/areq?p0=CTRL&p1=not%20a%20function%2C%20got%20undefined]在1.3.15 – 2015-04-21 10:21:46

17

引导()将调用AngularJS编译器为你,就像NG-应用。

// 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

+0

问题是我需要能够在需要时添加控制器,因为它们中有很多都是在开始时添加它们是不可行的。我已经尝试多次调用bootstrap,但是这也打破了现有的范围(等等)。 – 2013-03-07 06:49:00

+1

@DeathCarrot,我不知道如何动态添加额外的控制器。这可能帮助:https://github.com/matys84pl/angularjs-requirejs-lazy-controllers – 2013-03-07 21:09:42

+0

看起来你还需要了解可用的控制器,你引导(routes.js)前。不过,列出它们比加载它们要好。谢谢,我可能最终会这样做,除非我能找到完全动态完成的方法。 – 2013-03-08 07:22:06

1

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

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

这种方式,你可以从任何地方和实例化控制器编程,甚至是嵌套加载您的模板。

这里是一个工作示例加载内另一个控制器: http://plnkr.co/edit/x3G38bi7iqtXKSDE09pN

+0

其实学习更有棱角之后,我觉得这一切都是概念(或技术)是错误的。使用指令你可以做同样的事情,但更容易。 – 2015-03-05 01:20:27

7

我建议在运行时看看ocLazyLoad library,哪些寄存器(现有模块或控制器,服务等)的模块,并且还使用requireJs或其他此类库加载它们。

+0

哦,我Bajezus ......这正是大家在这个问题是寻找... +1注意这里... ocLazyLoad是所有照片和微芯片的袋子。 – 2015-12-03 18:32:46

+0

我也使用这个:D 有时候你想要一些库被延迟加载。因为它们依赖于其他库(需要首先加载)。所以我创建了一个同步加载器来完成这种类型的事情。让我知道是否有人想让我写这个装载机。因为它可能与此线程无关。 – r4ccoon 2016-05-20 20:11:43

1

我还需要在运行时从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"; 
}); 

这里的测试这样一个例子:http://refork.com/x4bc

希望这有助于。

+0

我用你的方法动态添加控制器。我想注入JavaScript对象到这个控制器。你能帮我么。 我已经问过关于stackoverflow的问题http://stackoverflow.com/questions/36597885/how-to-inject-javascript-object-into-dynamically-added-angular-controller – 2016-04-14 06:20:57

+0

是的,你可以使用RequireJS来加载你的JavaScript和得到你的控制器范围就像我在我的例子'mController.scope()' – 2016-04-14 10:26:34

+0

可以请你详细解释我使用我的问题。我对RequireJs了解不多。 – 2016-04-14 10:41:11

0
'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">

同样的结构可用于每个模块或模块内部模块

1

为什么不使用配置和用户界面 - 路由器?

它被加载在运行时,你有没有需要出示你的控制器中的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' 
        }, 

       } 

      }) 

    }); 
}, 

}; 
+0

因为它没有回答这个问题。他问**如何动态地**(延迟加载)控制器。如果它们从HTML或其他地方可见,则不是。 – Jeach 2018-03-04 20:04:10

0

这是我做的,2份真,使用纳克 - 控制器与其范围定义的功能,然后$控制器服务创建动态控制器: -

首先,HTML - 我们需要一个静态控制器,它将实例化动态控制器..

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

静态控制器'staticCtrl'定义一个名为'dynamicCtrl'的作用域成员,它被调用来创建动态控制器。 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()获得一个字符串(我们的动态代码可以来自任何地方)和然后$ controller控制器服务将采用预定义的控制器名称(正常情况下)或一个函数构造函数后跟构造函数参数(我们传入一个新的作用域) - Angular将注入(像任何控制器)到函数中,我们只是要求上面的$ scope和$ rootScope。

相关问题