Внедрить модуль динамически, только при необходимости
Я использую Require.js в сочетании с Angular.js.
Некоторые контроллеры нуждаются в огромных внешних зависимостях, которые не нужны другим, например, FirstController
требует Angular UI Codemirror. Это дополнительные 135 КБ, по крайней мере:
require([
"angular",
"angular.ui.codemirror" // requires codemirror itself
], function(angular) {
angular.module("app", [ ..., "ui.codemirror" ]).controller("FirstController", [ ... ]);
});
Я не хочу включать директиву и библиотеку Codemirror каждый раз, когда загружается моя страница, просто чтобы сделать 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.
6 ответов
Я уже давно пытаюсь смешать requirejs+Angular. До сих пор я опубликовал небольшой проект на Github ( angular-require-lazy), так как область действия слишком велика для встроенного кода или скриптов. Проект демонстрирует следующие моменты:
- Модули AngularJS загружаются лениво.
- Директивы тоже могут быть загружены.
- Существует "модуль" обнаружения и механизм метаданных (см. Мой другой любимый проект: require-lazy)
- Приложение автоматически разбивается на пакеты (т.е. работает с помощью r.js)
Как это сделать:
- Поставщики (например,
$controllerProvider
,$compileProvider
) захвачены изconfig
функция (метод, который я впервые увидел в https://github.com/matys84pl/angularjs-requirejs-lazy-controllers). - После начальной загрузки,
angular
заменяется нашей собственной оболочкой, которая может обрабатывать загруженные модули. - Инжектор захвачен и обещан.
- Модули AMD могут быть преобразованы в угловые модули.
Эта реализация удовлетворяет ваши потребности: она может лениво загружать модули Angular (по крайней мере, я использую ng-grid), определенно хакерская:) и не модифицирует внешние библиотеки.
Комментарии / мнения приветствуются.
(РЕДАКТИРОВАТЬ) Отличие этого решения от других в том, что оно не делает динамическое require()
звонки, таким образом, могут быть собраны с помощью r.js (и моего проекта require-lazy). Кроме того, идеи более или менее сходятся в различных решениях.
Всем удачи!
Внимание: используйте решение Nikos Paraskevopoulos, так как оно более надежное (я его использую) и имеет гораздо больше примеров.
Хорошо, я наконец-то узнал, как этого добиться, с краткой помощью с этим ответом.
Как я уже сказал в своем вопросе, это стало очень хакерским способом. Это включает применение каждой функции в _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
модуль зависит также и должен быть ленивым загрузочным модулем. Это связано с тем, что кэши провайдера и экземпляра, которые angular использует для своей службы $ инжектора, являются частными, и они не предоставляют метод регистрации новых модулей после завершения инициализации.
Таким образом, "хакерский" способ сделать это - отредактировать каждый из модулей, для которого вы хотите выполнить отложенную загрузку, чтобы требовать объекта отложенной загрузки модуля (в приведенном вами примере модуль находится в файле "appModules.js"), затем отредактируйте каждый из вызовов контроллера, директивы, фабрики и т. д. app.lazy.{same call}
вместо.
После этого вы можете продолжить следить за примером проекта, с которым вы связались, посмотрев, как лениво загружаются маршруты приложений (файл appRoutes.js показывает, как это сделать).
Не уверен, что это поможет, но удачи.
Существует директива, которая сделает это:
https://github.com/AndyGrom/loadOnDemand
пример:
<div load-on-demand="'module_name'"></div>
Я отправляю вам пример кода. Это работает нормально для меня. Поэтому, пожалуйста, проверьте это:
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>
Проблема с существующими методами ленивой загрузки состоит в том, что они делают то, что я хочу сделать сам.
Например, используя requirejs, я хотел бы просто позвонить:
require(['tinymce', function() {
// here I would like to just have tinymce module loaded and working
});
Однако это не работает таким образом. Зачем? Как я понимаю, AngularJS просто помечает модуль как "загружаемый в будущем", и если, например, я немного подожду, он сработает - я смогу его использовать. Поэтому в приведенной выше функции я хотел бы вызвать некоторую функцию, например loadPendingModules();
В моем проекте я создал простого провайдера ('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