Injetar módulo dinamicamente, somente se necessário

Estou usando o Require.js em combinação com o Angular.js.

Alguns controladores precisam de dependencies externas enormes que outros não precisam, por exemplo, o FirstController requer o Codemirror de interface angular . Isso é um extra de 135 kb, pelo menos:

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

Eu não quero ter que include a diretiva e o Codemirror lib toda vez que minha página for carregada apenas para deixar Angular feliz.
É por isso que estou carregando o controlador apenas quando a rota é atingida, como o que é feito aqui .

No entanto, quando eu preciso de algo como

 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) { // ... } ]); }); 

Como posso dizer ao Angular para injetar o módulo ui.codemirror (ou qualquer outro módulo) no módulo de aplicativo também?
Eu não me importo se é uma maneira rápida de conseguir isso, a menos que isso envolva modificar o código de dependencies externas.

Se for útil: estou executando o Angular 1.2.0.

Eu tenho tentado mixar requirejs + Angular por algum tempo agora. Eu publiquei um pequeno projeto no Github ( angular-require-preguiçoso ) com o meu esforço até agora, já que o escopo é muito grande para códigos embutidos ou violinos. O projeto demonstra os seguintes pontos:

  • Os módulos do AngularJS são carregados com preguiça.
  • As diretivas podem ser carregadas com preguiça também.
  • Existe um mecanismo de descoberta e metadados de “módulo” (veja meu outro projeto de estimação: require-lazy )
  • O aplicativo é dividido em pacotes automaticamente (ou seja, a construção com trabalhos r.js)

Como isso é feito:

  • Os provedores (por exemplo, $controllerProvider , $compileProvider ) são capturados de uma function config (técnica que vi pela primeira vez em angularjs-requirejs-lazy-controllers ).
  • Após o bootstraping, o angular é substituído pelo nosso próprio wrapper que pode manipular os módulos carregados com preguiça.
  • O injetor é capturado e fornecido como uma promise.
  • Os módulos AMD podem ser convertidos em módulos angulares.

Esta implementação satisfaz as suas necessidades: pode carregar módulos Angulares de carregamento lento (pelo menos o ng-grid que estou usando), é definitivamente hackish 🙂 e não modifica bibliotecas externas.

Comentários / opiniões são mais que bem-vindos.


(EDIT) A diferenciação desta solução de outras é que ela não faz chamadas dinâmicas require() , portanto, pode ser construída com r.js (e meu projeto require-lazy). Além disso, as ideias são mais ou menos convergentes entre as várias soluções.

Boa sorte a todos!

Atenção: use a solução de Nikos Paraskevopoulos, pois é mais confiável (estou usando), e tem muito mais exemplos.


Ok, eu finalmente descobri como conseguir isso com uma breve ajuda com esta resposta .

Como eu disse na minha pergunta, isso se tornou muito hacker. Envolve a aplicação de cada function na matriz _invokeQueue do módulo dependente no contexto do módulo de aplicativo.

É algo assim (preste mais atenção na function moduleExtender, por favor):

 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 }; }); 

Depois disso, eu preciso, por exemplo, fazer isso:

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

A chave para isso é que qualquer módulo do seu módulo de app também precisa ser um módulo de carregamento lento. Isso ocorre porque o provedor e a instância armazenam em cache que os usos angulares para seu serviço $ injector são particulares e não expõem um método para registrar novos módulos após a conclusão da boot.

Então, a maneira ‘hacky’ de fazer isso seria editar cada um dos módulos que você deseja carregar com preguiça para exigir um object de módulo de carregamento lento (no exemplo que você vinculou, o módulo está localizado no arquivo ‘appModules.js’), em seguida, edite cada controlador, diretiva, fábrica, etc., para usar app.lazy.{same call} .

Depois disso, você pode continuar seguindo o projeto de amostra ao qual você se vinculou, observando como as rotas de aplicativos são carregadas de forma preguiçosa (o arquivo ‘appRoutes.js’ mostra como fazer isso).

Não tenho certeza se isso ajuda, mas boa sorte.

Existe uma diretiva que fará isso:

https://github.com/AndyGrom/loadOnDemand

exemplo:

 

O problema com as técnicas de carga lenta existentes é que elas fazem coisas que eu quero fazer sozinho.

Por exemplo, usando o requirejs, gostaria de apenas chamar:

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

No entanto, não funciona dessa maneira. Por quê? Pelo que entendi, o AngularJS apenas marca o módulo como ‘a ser carregado no futuro’, e se, por exemplo, eu esperar um pouco, ele funcionará – eu poderei usá-lo. Então, na function acima eu gostaria de chamar algumas funções como loadPendingModules ();

No meu projeto eu criei um provedor simples (‘lazyLoad’) que faz exatamente isso e nada mais, então agora, se eu precisar ter algum módulo completamente carregado, posso fazer o seguinte:

 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 }]); }; // ........ }); 

aqui está o link para o arquivo de origem (licença MPL): https://github.com/lessmarkup/less-markup/blob/master/LessMarkup/UserInterface/Scripts/Providers/lazyload.js

Estou lhe enviando código de amostra. Está funcionando bem pra mim. Então, por favor, verifique isso:

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

E na página HTML: