0
votes

Say I have three modules and one main module like so:

//Module A
define([''], function(){
   function initialize(param1, param2){
      //initialize stuff
   }
   //other stuff

   return {
      initialize: initialize,
      //whatever else
   };
});

//Module B
define(['ModuleA'], function(ModuleA){
   function initialize(param2, param3){
      //initialize stuff using ModuleA
   }
   //other stuff

   return {
      initialize: initialize,
      //whatever else
   };
});

//Module C
define(['ModuleB'], function(ModuleB){
   function initialize(param4, param5){
      //initialize stuff using ModuleB
   }
   //other stuff

   return {
      initialize: initialize,
      //whatever else
   };
});

//main module
require(['ModuleA', 'ModuleB', 'ModuleC'], function(ModuleA, ModuleB, ModuleC){
   ModuleA.initialize(arg1, arg2);
   ModuleB.initialize(arg3, arg4);
   ModuleC.initialize(arg5, arg6);
});

The problem here is that there is temporal coupling between all the initialize calls in the main module. I, as the programmer, have to remember in what order the modules must be initialized. If ModuleB is initialized before ModuleA, then it will basically be using an uninitialized module because ModuleA hasn't been initialized yet. Now, I could use dependency injection in which I actually pass ModuleB and ModuleC their dependencies through arguments in the initialize method but that would defeat the purpose of requirejs which is to handle dependencies. I might as well just use script tags and pass dependencies manually, making sure each script is independent. I was interested to know if there is some sort of other solution. Thanks!

1

1 Answers

0
votes

You can do the module initialization in the define function and specify in the define([..] what are its dependencies:

//Module A
define(['SomeModule'], function(SomeModule){
  // initialization of the module

  return {
    //initializedModuleInterface..
  };
});

If you are stumbling upon circular dependencies between your modules, than you can in the interface you return from a module, deffer the need of the second module up until the usage of the module provided function for instance, by requiring it not in the define([..] but when the module itself get used: require("a").doSomething();

//Inside b.js:
define(["require", "a"],
    function(require, a) {
        //"a" in this case will be null if "a" also asked for "b",
        //a circular dependency.
        return function(title) {
            return require("a").doSomething();
        }
    }
);

http://requirejs.org/docs/api.html#circular

How to handle circular dependencies with RequireJS/AMD?

The initialization params themselves could be another module providing configuration params.


Edit 1: Add usage of requirejs as dynamic module definer and loader

If the need is for a structure of dynamic dependency injection mechanism, you still can use requirejs for that. Not sure if it was designed for it though:

1 . When you have the necessary config var in hand, then do a dynamic module define via requirejs

For ex:

angular.module('myModule')
.run(function () {
  define('myConf', [], function() {
    return {
      confA: 1,
      confB: 2
    };
  });
});

2 . In the modules that relies on the myConf module do as usual:

// Module: ModuleA
define(['myConf', function(myConf) {
  // initialization with myConf values.
  console.log(myConf.confA);

  return some_interface;
});

or

define(function(require) {
  var myConf = require('myConf');
  console.log(myConf.confA);

  // initialization with myConf values.

  return some_interface;
});

3 . In the lifecycle of the app, when ModuleA, ModuleB.. is needed then:

var moduleReq = 'moduleA';
require([moduleReq], function (moduleA) {
  // doSomething with moduleA
});

The trick here is the dynamic defining of a module with a specific name via define('myConf', [], function() {..}). It lets you define a module without the need of a file and dynamically anytime in the lifespan of the application. From what I understand it mainly used for requirejs bundling solutions.


Edit 2: Second approach - Using promises inside requirejs modules as inner dependency management.

Another approach that can be used is to use Promise inside requirejs modules to specify dependencies to wait on.

We can define a module main construct as a promise and resolve it via an interface init function when we have the necessary data.

1 . We specify every module as a file in the system as usual. 2 . In the confModule we want to be dynamically initialize, we build it in this pattern:

// confModule
define([], function () {
   var resolveMain;
   var rejectMain;

   var promise = new Promise(resolve, reject) {
       resolveMain = resolve;
       rejectMain = reject;
   }

   return {
       init: function (confVarA, confVarB) {
           try {
             var moduleInitialized = {
               // some preparation of confModule
               confVarA: confVarA,
               confVarB: confVarB
             };
             resolve(moduleInitialized);
           } 
           catch (e) {
              rejectMain(e);
           } 
       },

       then: function (successFn, errorFn) {
          return promise.then(successFn, errorFn);
       } 
   }
});

We resolve the promise outside of the constructor. Attach linked provides more info on the benefits and pitfalls of that pattern.

3 . In the dependent modules, we define them in the same pattern without needed the init functionality, and adding waiting on the promise of the confModule:

// moduleA
define(['confModule'], function (confModule) {
   var resolveMain;
   var rejectMain;

   var promise = confModule.then(function(confModuleData) {
      var moduleBInterface = {
        // prepare module b 
      };

      return moduleBInterface; // this make the promise return another promise to wait on for moduleBInterface return;
   };

   return {
       then: function (successFn, errorFn) {
          return promise.then(successFn, errorFn);
       } 
   }
});

4 . In our code, we can initalize confModule when we have the data we need:

define(['confModule', function (confModule) {
   // some async get of confData
   $.get(url, function (data) {
     confModule.init(data.a, data.b);
   });
});

5. In our code when using moduleA, we need to use it as a promise:

define(['moduleA'], fucntion (moduleA) {
  moduleA.then(function (moduleAInteface) {
    // do something with moduleA.
  });
});