64

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

这是一个片段,它根据 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);
});

JSF中。请注意,这是对实际事件链的简化,上面的行之间有各种异步调用和用户输入。

当我尝试运行上述代码时, $compile 返回的链接器会抛出:Argument 'Ctrl' is not a function, got undefined。如果我正确理解引导程序,它返回的注入器应该知道Foo模块,对吧?

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

我是否使用了正确的功能来执行此操作,或者我错过了什么?我知道这不是 Angular 的方式,但我需要将使用 Angular 的新组件添加到不使用 Angular 的旧页面中,而且我不知道引导模块时可能需要的所有组件。

更新:

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

4

8 回答 8

71

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

// 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未记录的模块。

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

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

于 2013-03-08T11:02:13.240 回答
17

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

小提琴

于 2013-03-06T16:16:20.370 回答
7

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

于 2014-10-14T19:09:19.950 回答
2

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

希望这可以帮助。

于 2015-03-25T22:49:52.817 回答
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

于 2014-06-03T01:11:29.127 回答
1

为什么不使用 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'
                    },

                }

            })

    });
},

};
于 2015-04-11T14:50:44.780 回答
1

这就是我所做的,实际上是 2 个部分,使用 ng-controller 及其范围定义的函数,然后使用 $controller 服务来创建动态控制器:-

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

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

静态控制器“staticCtrl”定义了一个名为“dynamicCtrl”的范围成员,它被调用来创建动态控制器。ng-controller 将按名称获取预定义的控制器,或者查看当前范围的同名功能..

.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。

于 2017-02-01T09:23:19.437 回答
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">

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

于 2015-04-06T20:21:42.677 回答