2013-03-06 136 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。

相關問題