7
\$\begingroup\$

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>
\$\endgroup\$
1
  • \$\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\$ Dec 23, 2015 at 22:42

2 Answers 2

2
\$\begingroup\$

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 array

  • This

    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 being null or undefined)

  • 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. The else was superfluous since you exit the function anyway if the url was already loaded

  • I would encode all the image extensions from if (fileType == "jpg" || fileType == "jpeg" || fileType == "png" || fileType == "gif") { into an array and use indexOf.

All in all, I like the code. `

\$\endgroup\$
2
\$\begingroup\$

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.

  1. Injects the resource into the head.
  2. Hooks into onload event (not working on all browsers) .
  3. Polls document.styleSheets for changes.
  4. 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

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.