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>