I wrote a little factory to dynamically load assets in angular resolve. Is it well structured and can it be made prettier?
app.factory('loadAssets', function ($q) {
var jsPath = "scripts/",
cssPath = "css/",
imagesPath = "images/",
head = document.getElementsByTagName("head")[0]; // define starting paths for each file tile
return {
startLoad: function (url) {
var fileType = url.split(".");
fileType = fileType[fileType.length - 1]; // we must get (length - 1) because filenames like jquery.min.js
/* prevent duplicate loading -
check if url exist in already loaded assets
1. If asset exist, return true and quit.
2. If asset doesn't exist - add it.
3. If loadedAssets is undefined (first time you load something) - create this array.
*/
if (this.loadedAssets == undefined || this.loadedAssets == null) {
this.loadedAssets = [];
}
if (this.loadedAssets.indexOf(url) >= 0) { // note that indexOf supported only in ie9+ , add fallback if you want to support legacy browsers.
return true;
} else {
this.loadedAssets.push.apply(this.loadedAssets, [url]);
// load js files
if (fileType == "js") {
var jsFile = document.createElement("script");
jsFile.src = jsPath + url;
head.appendChild(jsFile);
var waitforload = $q.defer();
jsFile.onload = function () {
waitforload.resolve(jsFile);
};
jsFile.onerror = function (e) {
waitforload.reject(e);
console.log("Could not load " + jsFile.src);
};
return waitforload.promise;
}
// load css files
if (fileType == "css") {
var cssFile = document.createElement("link");
cssFile.setAttribute("rel", "stylesheet");
cssFile.setAttribute("type", "text/css");
cssFile.setAttribute("href", cssPath+url);
head.appendChild(cssFile);
}
// load images
if (fileType == "jpg" || fileType == "jpeg" || fileType == "png" || fileType == "gif") {
var waitforload = $q.defer();
var image = new Image();
image.src = imagesPath + url;
image.onload = function () {
waitforload.resolve(image);
};
image.onerror = function (e) {
waitforload.reject(e);
console.log("Could not load " + image.src);
};
return waitforload.promise;
}
}
},
loadedAssets: this.loadedAssets
}
});
USAGE
app.config(function ($routeProvider, $controllerProvider) {
$routeProvider.when('/somepath', {
resolve: {
load: function ($q, loadAssets) {
files = ['somePathController.js', 'somePath.css', 'somePath.jpg'];
return $q.all(files.map(loadAssets.startLoad));
}
},
templateUrl: 'views/somePath.html'
}).otherwise({
redirectTo: '/somepath'
});
app.controllerProvider = $controllerProvider.register; // create reference to $controllerProvider.register - so we can dynamically register controllers oustide the app.config
});
somePathController.js
app.controllerProvider('somePathCtrl', function () {
// controller functionality here
});
somePath.html (view)
<div ng-controller="somePathCtrl">HTML HERE.</div>
-
\$\begingroup\$ I think this isn't going to scale well if you have to use your code base on different content, this will hurt really fast. For example, if you need to use your somePath view to show some other jpegs. You also have to load controllers during the config phase. It is also not going to minify. I'd write this as an answer, but you've already selected one as correct. \$\endgroup\$Amy Blankenship– Amy Blankenship2015年12月23日 22:42:51 +00:00Commented Dec 23, 2015 at 22:42
2 Answers 2
From a once over:
This
var fileType = url.split("."); fileType = fileType[fileType.length - 1]; // we must get (length - 1) because filenames like jquery.min.js
could be
//Get the last . separated string var fileType = url.split(".").slice(-1);
.slice(-1)
retrieves the last element in an arrayThis
if (this.loadedAssets == undefined || this.loadedAssets == null) { this.loadedAssets = []; }
could be
this.loadedAssets == this.loadedAssets || [];
this will assign
[]
if this.loadedAssets evaluates to false (by beingnull
orundefined
)This
if (this.loadedAssets.indexOf(url) >= 0) { // note that indexOf supported only in ie9+ , add fallback if you want to support legacy browsers. return true; } else {
should be
// indexOf suppor only in ie9+ , add fallback if you want to support legacy browsers. if (this.loadedAssets.indexOf(url) >= 0) { return true; }
The comment is on top which reads easier, but more importantly there is no
else
. Theelse
was superfluous since you exit the function anyway if theurl
was already loadedI would encode all the image extensions from
if (fileType == "jpg" || fileType == "jpeg" || fileType == "png" || fileType == "gif") {
into an array and useindexOf
.
All in all, I like the code. `
I have found a trick that may work for your case. This code is only for CSS, but it should be easy to extend with the other cases.
- Injects the resource into the head.
- Hooks into onload event (not working on all browsers) .
- Polls document.styleSheets for changes.
- Everything is wrapped in a promise within a Service
The routing would look like:
// Routing setup
.config(function ($routeProvider) {
$routeProvider
.when('/home', {
controller: 'homeCtrl',
templateUrl: 'home.tpl.html'
}).when('/users', {
controller: 'usersCtrl',
templateUrl: 'users.tpl.html',
resolve: {
load: function (injectCSS) {
return injectCSS.set("users", "users.css");
}
}
}).otherwise({
// default page
redirectTo: '/home'
});
})
On the Service:
.factory("injectCSS", ['$q', '$http', 'MeasurementsService', function($q, $http, MeasurementsService){
var injectCSS = {};
var createLink = function(id, url) {
var link = document.createElement('link');
link.id = id;
link.rel = "stylesheet";
link.type = "text/css";
link.href = url;
return link;
}
var checkLoaded = function (url, deferred, tries) {
for (var i in document.styleSheets) {
var href = document.styleSheets[i].href || "";
if (href.split("/").slice(-1).join() === url) {
deferred.resolve();
return;
}
}
tries++;
setTimeout(function(){checkLoaded(url, deferred, tries);}, 50);
};
injectCSS.set = function(id, url){
var tries = 0,
deferred = $q.defer(),
link;
if(!angular.element('link#' + id).length) {
link = createLink(id, url);
link.onload = deferred.resolve;
angular.element('head').append(link);
}
checkLoaded(url, deferred, tries);
return deferred.promise;
};
return injectCSS;
}])
Full code is available here