Skip to content

Commit

Permalink
feat: prevent duplicate loadings & add a cache busting param
Browse files Browse the repository at this point in the history
Fixes #38
  • Loading branch information
ocombe committed Jul 14, 2014
1 parent 91ed522 commit 5a5d7f1
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 57 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ You can put more than one template script in your template file, just make sure
</script>
```

The load service comes with a second optional parameter that you can use if you need to define some configuration for the requests (check: https://docs.angularjs.org/api/ng/service/$http#usage) but it only works with the templates (js and css default loaders don't use `$http`).
The load service comes with a second optional parameter that you can use if you need to define some configuration for the requests (check: https://docs.angularjs.org/api/ng/service/$http#usage).
The parameter `cache: false` works for all native loaders (**all requests are cached by default**), other parameters only works with the templates for now (js and css default loaders don't use `$http`).
```js
$ocLazyLoad.load(
['partials/template1.html', 'partials/template2.html'],
Expand Down
118 changes: 62 additions & 56 deletions src/ocLazyLoad.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
'use strict';
var regModules = ['ng'],
regInvokes = [],
filesLoaded = [],
ocLazyLoad = angular.module('oc.lazyLoad', ['ng']),
broadcast = angular.noop;

ocLazyLoad.provider('$ocLazyLoad', ['$controllerProvider', '$provide', '$compileProvider', '$filterProvider', '$injector', '$animateProvider',
function($controllerProvider, $provide, $compileProvider, $filterProvider, $injector, $animateProvider) {
var modules = {},
templates = [],
providers = {
$controllerProvider: $controllerProvider,
$compileProvider: $compileProvider,
Expand All @@ -26,8 +24,9 @@
// Let's get the list of loaded modules & components
init(angular.element(window.document));

this.$get = ['$timeout', '$log', '$q', '$templateCache', '$http', '$rootElement', '$rootScope', function($timeout, $log, $q, $templateCache, $http, $rootElement, $rootScope) {
var instanceInjector;
this.$get = ['$timeout', '$log', '$q', '$templateCache', '$http', '$rootElement', '$rootScope', '$cacheFactory', function($timeout, $log, $q, $templateCache, $http, $rootElement, $rootScope, $cacheFactory) {
var instanceInjector,
filesCache = $cacheFactory('ocLazyLoad');

if(!debug) {
$log = {};
Expand All @@ -41,9 +40,7 @@
};

broadcast = function broadcast(eventName, params) {
// console.log('broadcast');
if(events) {
// console.log(arguments);
$rootScope.$broadcast(eventName, params);
}
}
Expand All @@ -54,21 +51,32 @@
* @param path
* @returns promise
*/
var buildElement = function buildElement(type, path) {
var buildElement = function buildElement(type, path, params) {
var deferred = $q.defer(),
el, loaded;
el, loaded,
cacheBuster = function cacheBuster(url) {
var dc = new Date().getTime();
if(url.indexOf('?') != -1) {
if(url.substring(0, url.length - 1) === '&') {
return url + '_dc=' + dc;
}
return url + '&_dc=' + dc;
} else {
return url + '?_dc=' + dc;
}
};

// Switch in case more content types are added later (for example a native JS loader!)
// Switch in case more content types are added later
switch(type) {
case 'css':
el = document.createElement('link');
el.type = 'text/css';
el.rel = 'stylesheet';
el.href = path;
el.href = params.cache === false ? cacheBuster(path) : path;
break;
case 'js':
el = document.createElement('script');
el.src = path;
el.src = params.cache === false ? cacheBuster(path) : path;
break;
default:
deferred.reject(new Error('Requested type "' + type + '" is not known. Could not inject "' + path + '"'));
Expand All @@ -78,14 +86,13 @@
if((el['readyState'] && !(/^c|loade/.test(el['readyState']))) || loaded) return;
el.onload = el['onreadystatechange'] = null
loaded = 1;
if(filesLoaded.indexOf(path) === -1) {
filesLoaded.push(path);
broadcast('ocLazyLoad.fileLoaded', path);
if(angular.isUndefined(filesCache.get(path))) {
filesCache.put(path, true);
}
broadcast('ocLazyLoad.fileLoaded', path);
deferred.resolve();
}
el.onerror = function(e) {
console.log('error');
deferred.reject(new Error('Unable to load '+path));
}
el.async = 1;
Expand All @@ -100,12 +107,13 @@
* @type Function
* @param paths array list of js files to load
* @param callback to call when everything is loaded. We use a callback and not a promise
* @param params object config parameters
* because the user can overwrite jsLoader and it will probably not use promises :(
*/
jsLoader = function(paths, callback) {
jsLoader = function(paths, callback, params) {
var promises = [];
angular.forEach(paths, function loading(path) {
promises.push(buildElement('js', path));
promises.push(buildElement('js', path, params));
});
$q.all(promises).then(function success() {
callback();
Expand All @@ -122,12 +130,13 @@
* @type Function
* @param paths array list of css files to load
* @param callback to call when everything is loaded. We use a callback and not a promise
* @param params object config parameters
* because the user can overwrite cssLoader and it will probably not use promises :(
*/
cssLoader = function(paths, callback) {
cssLoader = function(paths, callback, params) {
var promises = [];
angular.forEach(paths, function loading(path) {
promises.push(buildElement('css', path));
promises.push(buildElement('css', path, params));
});
$q.all(promises).then(function success() {
callback();
Expand All @@ -143,42 +152,33 @@
* templatesLoader function
* @type Function
* @param paths array list of css files to load
* @param params object config parameters for $http
* @param callback to call when everything is loaded. We use a callback and not a promise
* @param params object config parameters for $http
* because the user can overwrite templatesLoader and it will probably not use promises :(
*/
templatesLoader = function(paths, params, callback) {
templatesLoader = function(paths, callback, params) {
if(angular.isString(paths)) {
paths = [paths];
}
if(angular.isFunction(params)) {
callback = params;
params = {};
}
if(angular.isUndefined(params)) {
params = {};
}
var promises = [];
angular.forEach(paths, function(url) {
var deferred = $q.defer();
promises.push(deferred.promise);
if(templates.indexOf(url) === -1 || (angular.isDefined(params) && params.cache === false)) {
$http.get(url, params).success(function(data) {
angular.forEach(angular.element(data), function(node) {
if(node.nodeName === 'SCRIPT' && node.type === 'text/ng-template') {
$templateCache.put(node.id, node.innerHTML);
}
});
templates.push(url);
deferred.resolve();
}).error(function(data) {
var err = 'Error load template "' + url + '": ' + data;
$log.error(err);
deferred.reject(new Error(err));
$http.get(url, params).success(function(data) {
angular.forEach(angular.element(data), function(node) {
if(node.nodeName === 'SCRIPT' && node.type === 'text/ng-template') {
$templateCache.put(node.id, node.innerHTML);
}
});
} else {
if(angular.isUndefined(filesCache.get(url))) {
filesCache.put(url, true);
}
deferred.resolve();
}
}).error(function(data) {
var err = 'Error load template "' + url + '": ' + data;
$log.error(err);
deferred.reject(new Error(err));
});
});
return $q.all(promises).then(function success() {
callback();
Expand All @@ -196,12 +196,14 @@
promises = [];

angular.forEach(paths, function(path) {
if(/\.css[^\.]*$/.test(path)) {
cssFiles.push(path);
} else if(/\.(htm|html)[^\.]*$/.test(path)) {
templatesFiles.push(path);
} else {
jsFiles.push(path);
if(angular.isUndefined(filesCache.get(path)) || params.cache === false) {
if(/\.css[^\.]*$/.test(path)) {
cssFiles.push(path);
} else if(/\.(htm|html)[^\.]*$/.test(path)) {
templatesFiles.push(path);
} else {
jsFiles.push(path);
}
}
});

Expand All @@ -214,20 +216,20 @@
} else {
cssDeferred.resolve();
}
});
}, params);
promises.push(cssDeferred.promise);
}

if(templatesFiles.length > 0) {
var templatesDeferred = $q.defer();
templatesLoader(templatesFiles, params, function(err) {
templatesLoader(templatesFiles, function(err) {
if(angular.isDefined(err) && templatesLoader.hasOwnProperty('ocLazyLoadLoader')) {
$log.error(err);
templatesDeferred.reject(err);
} else {
templatesDeferred.resolve();
}
});
}, params);
promises.push(templatesDeferred.promise);
}

Expand All @@ -240,7 +242,7 @@
} else {
jsDeferred.resolve();
}
});
}, params);
promises.push(jsDeferred.promise);
}

Expand Down Expand Up @@ -278,11 +280,15 @@
moduleName,
errText;

if(angular.isUndefined(params)) {
params = {};
}

// If module is an array, break it down
if(angular.isArray(module)) {
// Resubmit each entry as a single module
angular.forEach(module, function(m) {
deferredList.push(self.load(m));
deferredList.push(self.load(m, params));
});

// Resolve the promise once everything has loaded
Expand Down Expand Up @@ -404,7 +410,7 @@
// Check if the dependency has any files that need to be loaded. If there are, push a new promise to the promise list.
if(requireEntry.hasOwnProperty('files') && requireEntry.files.length !== 0) {
if(requireEntry.files) {
promisesList.push(filesLoader(requireEntry.files).then(function() {
promisesList.push(filesLoader(requireEntry.files, params).then(function() {
return loadDependencies(requireEntry)
}));
}
Expand All @@ -415,7 +421,7 @@
return $q.all(promisesList);
}

filesLoader(config.files).then(function success() {
filesLoader(config.files, params).then(function success() {
if(moduleName === null) {
deferred.resolve(module);
} else {
Expand Down

0 comments on commit 5a5d7f1

Please sign in to comment.