Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
nateabele's autoload patch, comments by stu-salsbury
/**
* Naive AngularJS module autoloader; depends on require.js.
*
* Usage:
*
* angular.autoLoad({
* 'ngResource': '/js/angular-resource-1.0.1.min.js',
* 'google-maps': '/js/angular-google-maps.js',
* 'ui.bootstrap': '/js/ui/ui-bootstrap-custom-tpls-0.4.0.min.js',
* 'module.split.over.multiple.files': ['/js/file1.js', '/js/file2.js']
* });
*
* Then initialize your modules with their dependencies as normal, and the correct scripts will be
* autoloaded appropriately.
*
* Notes:
* As mentioned, this is currently implemented with required.js. It should be rewritten using $q.
* ALSO, Angular must be boostrapped manually (http://docs.angularjs.org/guide/bootstrap) *after*
* your autoload configuration is initialized, and *before* your application modules are loaded.
*
* SCS: $q would give us asynchrony, but without AMD, aren't we left with ngInclude, which doesn't support
* cross-origin loading, or jQuery (which does)? I guess this could be factored out to give the user choices...
*
* SCS: I don't yet understand the comment about boostrap timing... hoping to be able to add dependencies
* at runtime and rebootrap -- looks like this might be possible.
*
* SCS: would like to factor out the autoLoad url map and expose this as a service. Perhaps create a different service
* for module/component-to-URL mappings?
*
*/
(function(angular) {
//backup the core module and boostrap functions
//track the number of outstanding requests
var _module = angular.module, _bootstrap = angular.bootstrap, _requests = 0, _inited = false;
var _loadMap = {}, _invokeQueue = {}, _loaded = {}, _funcCache = {}, _ = {};
//takes array of items or single
//item and puts it into the list
//obj if they/it are/is not already
//in list
var _appendAndFlatten = function(list, item) {
if (!angular.isArray(item)) {
if (list.indexOf(item) === -1) {
list.push(item);
}
return list;
}
angular.forEach(item, function(i) {
if (list.indexOf(i) === -1) {
list.push(i);
}
});
return list;
};
angular.extend(angular, {
//overwrite the module function
module: function(name, requires, configFn) {
if (!requires) {
//no dependencies -- just call the core function
return _module.call(angular, name, requires, configFn);
}
//if we get here, there are dependencies
//break them up and load them individually
var autoLoad = [];
//put all specified module dependencies that are not yet loaded
//and *ARE* in the _loadMap into the autoLoad list
angular.forEach(requires, function(mod) {
try {
//try to retrieve a module
angular.module(mod);
} catch (e) {
if (!e.toString().match(/No module/)) {
//something went wrong, and it is NOT that the
//module has yet to be loaded -- throw it
throw e;
}
//if we get here, then the module hasn't been loaded
if (_loadMap[mod]) {
//if the module is in the _loadMap, then
//ensure it is in the autoLoad list.
//if the _loadMap doesn't have a URL for the module
//then it won't be loaded
autoLoad = _appendAndFlatten(autoLoad, _loadMap[mod]);
}
}
});
//there are no dependencies yet to be loaded (other than 'ng'),
//so just load the module with the core function
if (!autoLoad.length || (autoLoad.length === 1 && autoLoad[0] === "ng")) {
_inited = true;
return _module.call(angular, name, requires, configFn);
}
//if we get here, we need to manually load the module (?)
//load the main module using core (without its dependencies)
//what we're getting back is the invokeQueue
var module = _module.call(angular, name, [], configFn);
//loop the module's properties
angular.forEach(module, function(val, key) {
if (!angular.isFunction(val)) {
//if it's not a function, do nothing
return;
}
//the val is one of the invokeLater functions
// (?or maybe the run function?);
//replace the function
module[key] = function() {
//when this function is called, it adds the property name and the args
//to the invokeQueue array at the module's name
_invokeQueue[name].push([key, Array.prototype.slice.call(arguments)]);
//and returns the module
return module;
};
//put the function itself in the cache keyed by its original name
_funcCache[key] = val;
});
// set up the _invokeQueue array for the module
_invokeQueue[name] = [];
//begin the loading of modules set up for autoLoad
console.log("loading", autoLoad);
//load the modules using AMD
require(autoLoad, function() {
//get the config functions from the cache
//and put them back in the module
for (var n in _funcCache) {
module[n] = _funcCache[n];
}
//put the dependencies in the module
module.requires = requires;
//if there was a config function, put it back
if (angular.isFunction(configFn)) {
module.config(configFn);
}
//loop the invoke queue for the module
//and invoke the functions
for (var i = 0; i < _invokeQueue[name].length; i++) {
var call = _invokeQueue[name][i];
module[call[0]].apply(module, call[1]);
}
//decrement the request counter
//this is happening out of process since we're in the
//require function
_requests--;
//if there is was a bootstrap function,
//invoke the reference we grabbed to it
//IF we've already initialized
//this *might* (?) allow to re-bootstrap
//after subsequent calls to module
if (_.businessTime) {
_.businessTime();
}
});
//one more outstanding request
//this is in-process so it happens before the require callback above
_requests++;
//initialized -- everything is set up to go
_inited = true;
return module;
},
//add properties of the modules object
//to the _loadMap
autoLoad: function(modules) {
angular.extend(_loadMap, modules);
},
//replace the angular bootstrap function
bootstrap: function() {
//grab the args
var args = Array.prototype.slice.call(arguments);
//this invokes the core boostrap function
//IF we've initialized already
//and there are no outstanding requests
_.businessTime = function() {
if (_inited && _requests === 0) {
return _bootstrap.apply(angular, args);
}
};
_.businessTime();
}
});
})(angular);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.