3
\$\begingroup\$

I got bored had time to kill in work and was creating a few things using plain JS (no frameworks allowed! I wanted to get used to progressive enhancement). I realized I would run into a problem if I wanted to access things in other scopes that might be loaded after my current file, and I didn't want to have to explicitly order the script tags.

I also figured that assigning to the window is kinda 'meh' and I would like to avoid it where possible to avoid exposing global state.

As a result, I wrote a quick n dirty dependency injector for the browser. It doesn't load files asynchronously (i.e, it's not RequireJS), but it's just meant to be simple enough for a small website.

Unfortunately it will only work with ES6-compliant browsers, unless you have a Promise polyfill. I consider this a fair enough trade off since I much prefer promises to callbacks.

;(function(window, Promise, exports) {
  'use strict';
  var types = {};

  /**
   * Register a type with the injector.
   * @param  {String} name         The name of the type to register.
   * @param  {Function} constructor  The constructor for the injector. Constructors can be regular function; `this` will be bound to the constructor function itself. Constructs may register their dependencies by placing an $inject field that is a string array. Each dependency will be resolved before instantiating the constructor. If the dependency cannot be fulfilled, an Error will be thrown.
   */
  exports.register = function(name, constructor) {
    var bundle = {
      dependencies: constructor.$inject || [],
      constructor: constructor
    };
    types[name] = bundle;
  };


  var resolveQueue = [];
  /**
   * Resolve a type. Note that this function will do nothing until the DOM has been loaded (this allows for the injector to be sure it has found every dependency).
   * @param  {String} name The name of the type to resolve.
   * @return {Promise<Object>}      A promise that will resolve to the resolved object, or reject upon error.
   */
  exports.resolve = function(name) {
    return new Promise(function(resolve, reject) {
      resolveQueue.push(function() {
        var bundle = types[name];
        if(bundle === undefined) {
          reject(new Error('could not find type: '+ name));
          return;
        }
        var dependencies = [];
        for (var i = 0; i < bundle.dependencies.length; i++) {
          var dependency = exports.resolve(bundle.dependencies[i]);
          dependencies.push(dependency);
        };
        return Promise.all(dependencies)
          .then(function(resolvedDependencies) {
            resolve(bundle.constructor.apply(bundle.constructor, resolvedDependencies));
          });
      });
    });
  };

  // Only process the resolve queue after we have loaded.
  // This is because we may not have the guarantee that all of our scripts
  // (and thus types) have been loaded yet, so some scripts loaded in the incorrect
  // order may depend on other scripts that are not loaded yet.
  window.addEventListener('load', function() {
    for (var i = 0; i < resolveQueue.length; i++) {
      resolveQueue[i]();
    };
  });
})(window, Promise, window.inject = window.inject || {});

Should be fairly simple and lightweight to use: inject.register(typeName, constructor) to register a type, inject.resolve(name) to resolve a type.

Here is a working example (you may review this, although it's just here to provide an example for how I want this to be used, rather than actual review code):

;(function(inject, window) {
  'use strict';
  if(inject === undefined) {
    throw new Error('I require my dependency injector to function!');
  }

  var factories = {};

  function create(querySelector, onCreated) {
    factories[querySelector] = onCreated;
  };

  var createDirectives = function(document) {
    for (var querySelector in factories) {
      if (factories.hasOwnProperty(querySelector)) {
        var factory = factories[querySelector];
        var elements = document.querySelectorAll(querySelector);
        for (var i = 0; i < elements.length; i++) {
          factory(elements[i]);
        };
      }
    }
  };

  inject.register('directive', function() {
    return {
      create: create
    };
  });

  window.addEventListener('load', function() {
    createDirectives(document);
  })
})(window.inject, window);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.