动态加载 AngularJS 控制器

IT技术 javascript angularjs
2021-01-14 09:56:49

我有一个现有页面,我需要将一个带有可以动态加载的控制器的 angular 应用程序放入其中。

这是一个片段,它根据 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请注意,这是对实际事件链的简化,上面的行之间有各种异步调用和用户输入。

当我尝试运行上面的代码时, $compile 返回的链接器抛出:Argument 'Ctrl' is not a function, got undefined. 如果我正确理解了 bootstrap,它返回的注入器应该知道该Foomodule,对吗?

相反,如果我使用 制作了一个新的注入器angular.injector(['ng', 'Foo']),它似乎可以工作,但它会创建一个新的注入器,该注入器$rootScope不再与Foo引导module的元素的作用域相同

我是使用正确的功能来做到这一点还是我遗漏了什么?我知道这不是 Angular 的方式,但我需要将使用 Angular 的新组件添加到不使用 Angular 的旧页面,而且我不知道引导module时可能需要的所有组件。

更新:

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

6个回答

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

// 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并使用在它真正不应该使用的地方(在引导程序之后)。此外,在注册之前似乎没有一种简单的方法可以获取用于定义控制器的函数,因此我需要遍历module的_invokeQueue,这是未记录的。

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

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

它也给我错误![错误:[NG:AREQ] errors.angularjs.org/1.2.26/ng/...
2021-03-21 09:56:49
嗯......它在尝试调用.injector()函数时给我一个错误,调用它未定义。我正在使用 angular1.2.23。它在较新的版本中是否已被弃用?
2021-03-23 09:56:49
那么过滤器呢?
2021-03-25 09:56:49
UPDATE 2 中的小提琴非常完美,正是我所需要的。
2021-03-31 09:56:49
在 1.3.15 中对我有用。如果您可以在 jsfiddle/plunker 中重现它,那么我可以看看它,否则诊断起来有点困难。
2021-03-31 09:56:49

bootstrap() 会为你调用 AngularJS 编译器,就像 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']);

小提琴

@DeathCarrot,我不知道如何动态添加额外的控制器。这可能会有所帮助:github.com/matys84pl/angularjs-requirejs-lazy-controllers
2021-03-12 09:56:49
@Mark Rajcok 如何传入DeathCarrot控制器而不是控制器内部的硬编码值?
2021-03-15 09:56:49
看起来您仍然需要在引导之前了解可用的控制器(routes.js)。尽管如此,列出它们比加载它们更好。谢谢,除非我能找到一种完全动态的方法,否则我可能最终会这样做。
2021-03-20 09:56:49
谢谢马克,这正是我要找的
2021-03-28 09:56:49
问题是我需要能够在需要时添加控制器,在开始时添加它们是不可行的,因为它们很多。我试过多次调用 bootstrap 但这也破坏了现有的范围(除其他外)。
2021-04-11 09:56:49

我建议查看ocLazyLoad 库,它在运行时注册module(或现有module上的控制器、服务等),并使用 requireJs 或其他此类库加载它们。

哦,我的 Bajezus ......这正是这个问题中的每个人都在寻找的......这里的关注 +1...... ocLazyLoad 就是这样和一袋微芯片。
2021-03-20 09:56:49
我也使用这个 :D 有时您希望某些库按顺序延迟加载。因为它们依赖于其他库(需要先加载)。所以我创建了一个同步加载器来做这种事情。如果有人想让我写这个加载器,请告诉我。因为它可能与此线程无关。
2021-03-26 09:56:49

我还需要添加多个视图并在运行时从 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";
});

这是一个测试这个的例子:https : //snippet.run/x4bc

希望这可以帮助。

是的,您可以使用 RequireJS 加载您的 javascript 并获取您的控制器范围,就像我在我的示例中所做的那样 mController.scope()
2021-03-22 09:56:49
你能用我的问题详细解释我吗?我对 RequireJs 了解不多。
2021-03-31 09:56:49
我用你的方法动态添加控制器。我想向这个控制器注入 javascript 对象。你能帮我么。我在 stackoverflow 上问过问题stackoverflow.com/questions/36597885/...
2021-04-05 09:56:49

我刚刚改进了 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

实际上,在学习了更多 Angular 之后,我认为这一切在概念上(或技术上)都是错误的。使用指令可以做同样的事情,但要容易得多。
2021-04-08 09:56:49