0

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!

TheMAAAN
  • 501
  • 5
  • 15

1 Answers1

0

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.
  });
});
Community
  • 1
  • 1
elpddev
  • 4,314
  • 4
  • 26
  • 47
  • I've actually thought about that already but I need the initialization function because I need to pass in arguments (in this case from the main module). If I were to do initialization that way then I wouldn't be able to pass in any information. Thanks for your input though! – TheMAAAN Apr 19 '17 at 20:58
  • I think you are trying to use requirejs as a general dependency injection mechanism with an injector at hand. I've updated my answer. Not sure if it was intended for the usage though :) – elpddev Apr 19 '17 at 22:12
  • But it seems like a common problem to pass data from main module to other modules to initialize them.. why would RequireJS not be intended for that and how do so many big software projects use it then? Is there another way of structuring programs that they use that I am not aware of?? With regards to your edit, are you using angular in "step 1"? – TheMAAAN Apr 20 '17 at 00:27
  • 1. Angular is just an example I use to initialize the main configuration module sometime in the lifecycle of the app not at the start in the seed test project I've checked this for. You can do it also with jquery document ready, or after ajax call. 2. `Not intended` is just my interpretation. Just did not see that usage in my previous projects. But this is a clean and valid solution just the same. – elpddev Apr 20 '17 at 04:21
  • 3. It is a common problem. You mainly solve it by using a module framework with dependency injection mechanism like Angular for instance. But they actually doing the same design pattern of thing. Specifying dependencies in code and waiting on them. http://stackoverflow.com/questions/16286605/angularjs-initialize-service-with-asynchronous-data#16288468 – elpddev Apr 20 '17 at 04:21
  • About requirejs, requirejs is mainly intended for solving loading of modules represented by files. But that does not stops us from using it in more general way in describing module not as a file but something we generate and register it in requirejs injector registrary. – elpddev Apr 20 '17 at 10:13
  • One thing I don't understand is that if you're going to use another dependency injection mechanism then what would be the point of requirejs? The other mechanism would be able to solve all of your issues yes? Also, in your "step 1" you said that you would have to have the "necessary config var in hand". What did you mean by this? Just for extra clarity, I would like to add that the information I am passing in from the main module is generated at runtime it's not constants or anything like that. Thanks for your link btw it was very helpful! – TheMAAAN Apr 21 '17 at 03:41
  • From RequireJs - `RequireJS is a JavaScript file and module loader`. Mainly used for handling loading of script files and on top of it, having a nice dependency management for loading them by required order. Angular module system is to arrange dependencies of instances in running code. You can argue that it is the same in the sense of doing DI pattern management in the latter stage of the app lifecycle ,yes. – elpddev Apr 21 '17 at 06:35
  • "necessry config var in hand" meaning that using this pattern, when you are ready and you have the info you need, then you can generate dynamically the config module in requirejs. As in the example, you can do it after Angular is running, after an ajax request to the server to get some info, after click of a button or other event. – elpddev Apr 21 '17 at 06:39
  • Ohh okay I understand. But wouldn't requirejs throw an error if you depend on some dependency but it hasn't been defined yet in the code? So in step 2 where you actually use 'myConf', you would get an error because if lets say the event hadn't happened yet or something. – TheMAAAN Apr 21 '17 at 19:28
  • Yes, you need to take care and make sure you call the dependent modules after you define the conf module. You can think of it as the life cycle stage where you write the file modules which in a sense is what we do here. – elpddev Apr 21 '17 at 20:42
  • No problem. Hope it helped. Just for more completeness, another approach I thought of is using promises inside requirejs modules as inner dependency management. I've added it to the answer. – elpddev Apr 22 '17 at 07:24