64

動的にロードできるコントローラーを備えた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. Fooブートストラップを正しく理解していれば、それが返すインジェクターはモジュールについて知っているはずですよね?

代わりに を使用して新しいインジェクターを作成すると、機能しているように見えますが、モジュールがブートストラップされた要素と同じスコープではない新しいインジェクターangular.injector(['ng', 'Foo'])が作成されます。$rootScopeFoo

これを行うために適切な機能を使用していますか、それとも見逃しているものがありますか? これが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() は 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']);

フィドル

于 2013-03-06T16:16:20.370 に答える
7

実行時にモジュール (または既存のモジュールのコントローラー、サービスなど) を登録し、requireJs などのライブラリを使用してそれらをロードする ocLazyLoad ライブラリを確認することをお勧めします

于 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 が挿入されてコンパイルされ、2 番目のコントローラーにリンクされます。

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 によって書かれた関数を改良して、すべてのことを 1 回の呼び出しで実行できるようにしました。

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 に答える