dynamic注入模块,只有在需要的情况下

我将Require.js与Angular.js结合使用。

有些控制器需要其他人不需要的巨大外部依赖,例如, FirstController需要Angular UI Codemirror 。 这是一个额外的135 kb,至less:

 require([ "angular", "angular.ui.codemirror" // requires codemirror itself ], function(angular) { angular.module("app", [ ..., "ui.codemirror" ]).controller("FirstController", [ ... ]); }); 

我不想在每次加载页面的时候都join这个指令和Codemirror lib,这样就可以让Angular变得更加快乐。
这就是为什么我现在正在加载控制器只有当路线被击中, 就像这里所做的一样 。

但是,当我需要类似的东西

 define([ "app", "angular.ui.codemirror" ], function(app) { // ui-codemirror directive MUST be available to the view of this controller as of now app.lazy.controller("FirstController", [ "$scope", function($scope) { // ... } ]); }); 

我如何告诉Angular在应用程序模块中注入ui.codemirror模块(或任何其他模块)?
我不在乎这是否是一种骇人的方法来实现这一点,除非涉及到修改外部依赖的代码。

如果有用的话:我正在运行Angular 1.2.0。

我一直在尝试混合使用requirejs + Angular一段时间。 目前为止,我在Github( angular-require-lazy )中发布了一个小项目,因为范围对于内联代码或小提琴来说太大了。 该项目展示了以下几点:

  • AngularJS模块被延迟加载。
  • 指令也可以被延迟加载。
  • 有一个“模块”发现和元数据机制(请参阅我的其他宠物项目: 需要懒惰 )
  • 应用程序被自动拆分为捆绑(即使用r.js作品构build)

它是如何完成的:

  • 提供者(例如$controllerProvider$compileProvider )是从一个config函数(我在angularjs-requirejs-lazy-controllers中首次看到的技术)捕获的。
  • 引导之后, angular被replace为我们自己的包装器,可以处理延迟加载的模块。
  • 注射器被捕获并作为承诺提供。
  • AMD模块可以转换为Angular模块。

这个实现满足你的需求:它可以延迟加载Angular模块(至less是我使用的ng-grid),肯定是hackish :)而且不会修改外部库。

评论/意见比欢迎。


(EDIT)这个解决scheme与其他的不同之处在于它不会执行dynamic的require()调用,因此可以使用r.js(以及我的require-lazy project)来构build。 除此之外,各种解决scheme中的想法或多或less地趋于一致。

祝你们好运!

注意:使用Nikos Paraskevopoulos的解决scheme,因为它更可靠(我正在使用它),并且有更多的例子。


好的,我终于find了如何实现这个与这个答案简短的帮助。

正如我在我的问题中所说的那样,这已经变得非常黑客。 它将在应用程序模块的上下文中应用依赖模块的_invokeQueue数组中的每个函数。

这是这样的(请在moduleExtender函数中多加注意):

 define([ "angular" ], function( angular ) { // Returns a angular module, searching for its name, if it's a string function get( name ) { if ( typeof name === "string" ) { return angular.module( name ); } return name; }; var moduleExtender = function( sourceModule ) { var modules = Array.prototype.slice.call( arguments ); // Take sourceModule out of the array modules.shift(); // Parse the source module sourceModule = get( sourceModule ); if ( !sourceModule._amdDecorated ) { throw new Error( "Can't extend a module which hasn't been decorated." ); } // Merge all modules into the source module modules.forEach(function( module ) { module = get( module ); module._invokeQueue.reverse().forEach(function( call ) { // call is in format [ provider, function, args ] var provider = sourceModule._lazyProviders[ call[ 0 ] ]; // Same as for example $controllerProvider.register("Ctrl", function() { ... }) provider && provider[ call[ 1 ] ].apply( provider, call[ 2 ] ); }); }); }; var moduleDecorator = function( module ) { module = get( module ); module.extend = moduleExtender.bind( null, module ); // Add config to decorate with lazy providers module.config([ "$compileProvider", "$controllerProvider", "$filterProvider", "$provide", function( $compileProvider, $controllerProvider, $filterProvider, $provide ) { module._lazyProviders = { $compileProvider: $compileProvider, $controllerProvider: $controllerProvider, $filterProvider: $filterProvider, $provide: $provide }; module.lazy = { // ...controller, directive, etc, all functions to define something in angular are here, just like the project mentioned in the question }; module._amdDecorated = true; } ]); }; // Tadaaa, all done! return { decorate: moduleDecorator }; }); 

完成之后,我只需要这样做:

 app.extend( "ui.codemirror" ); // ui.codemirror module will now be available in my application app.controller( "FirstController", [ ..., function() { }); 

关键在于你的app模块依赖的任何模块也需要成为一个懒惰的加载模块。 这是因为提供者和实例cachingangular度使用它的$注入服务是私有的,并且它们在初始化完成后不公开一个注册新模块的方法。

所以,这样做的“黑客”方法是编辑你希望延迟加载的模块,以便需要一个延迟加载模块对象(在你链接的例子中,模块位于文件“appModules.js”中)然后编辑每个控制器,指令,工厂等调用来使用app.lazy.{same call}来代替。

之后,通过查看应用程序path如何延迟加载(“appRoutes.js”文件显示如何执行此操作),可以继续关注已链接的示例项目。

不太确定这是否有帮助,但祝你好运。

有一个指令可以做到这一点:

https://github.com/AndyGrom/loadOnDemand

例:

 <div load-on-demand="'module_name'"></div> 

现有的懒惰加载技术的问题是,他们做自己想做的事情。

例如,使用requirejs,我想打电话给:

 require(['tinymce', function() { // here I would like to just have tinymce module loaded and working }); 

但是这样做不起作用。 为什么? 据我所知,AngularJS只是把这个模块标记为“将来要加载”,如果我稍微等一下,它就会起作用 – 我将能够使用它。 所以在上面的函数中,我想调用一些函数,如loadPendingModules();

在我的项目中,我创build了一个简单的提供者('lazyLoad'),它完成了这件事情,而现在,所以现在,如果我需要一些模块完全加载,我可以做到以下几点:

 myApp.controller('myController', ['$scope', 'lazyLoad', function($scope, lazyLoad) { // ........ $scope.onMyButtonClicked = function() { require(['tinymce', function() { lazyLoad.loadModules(); // and here I can work with the modules as they are completely loaded }]); }; // ........ }); 

这里是链接到源文件(MPL许可证): https : //github.com/lessmarkup/less-markup/blob/master/LessMarkup/UserInterface/Scripts/Providers/lazyload.js

我给你发送示例代码。 它对我来说工作得很好。 所以请检查一下:

 var myapp = angular.module('myapp', ['ngRoute']); /* Module Creation */ var app = angular.module('app', ['ngRoute']); app.config(['$routeProvider', '$controllerProvider', function ($routeProvider, $controllerProvider) { app.register = { controller: $controllerProvider.register, //directive: $compileProvider.directive, //filter: $filterProvider.register, //factory: $provide.factory, //service: $provide.service }; // 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) { app.register.controller(controllerName, call[2][1]); } } } var tt = { loadScript: function (path) { var result = $.Deferred(), script = document.createElement("script"); script.async = "async"; script.type = "text/javascript"; script.src = path; script.onload = script.onreadystatechange = function (_, isAbort) { if (!script.readyState || /loaded|complete/.test(script.readyState)) { if (isAbort) result.reject(); else { result.resolve(); } } }; script.onerror = function () { result.reject(); }; document.querySelector(".shubham").appendChild(script); return result.promise(); } } function stripScripts(s) { var div = document.querySelector(".shubham"); div.innerHTML = s; var scripts = div.getElementsByTagName('script'); var i = scripts.length; while (i--) { scripts[i].parentNode.removeChild(scripts[i]); } return div.innerHTML; } function loader(arrayName) { return { load: function ($q) { stripScripts(''); // This Function Remove javascript from Local var deferred = $q.defer(), map = arrayName.map(function (obj) { return tt.loadScript(obj.path) .then(function () { registerController(obj.module, obj.controller); }) }); $q.all(map).then(function (r) { deferred.resolve(); }); return deferred.promise; } }; }; $routeProvider .when('/first', { templateUrl: '/Views/foo.html', resolve: loader([{ controller: 'FirstController', path: '/MyScripts/FirstController.js', module: 'app' }, { controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' }]) }) .when('/second', { templateUrl: '/Views/bar.html', resolve: loader([{ controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' }, { controller: 'A', path: '/MyScripts/anotherModuleController.js', module: 'myapp' }]) }) .otherwise({ redirectTo: document.location.pathname }); }]) 

而在HTML页面中:

 <body ng-app="app"> <div class="container example"> <!--ng-controller="testController"--> <h3>Hello</h3> <table> <tr> <td><a href="#/first">First Page </a></td> <td><a href="#/second">Second Page</a></td> </tr> </table> <div id="ng-view" class="wrapper_inside" ng-view> </div> <div class="shubham"> </div> </div>