35

How much can I stretch RequireJS to provide dependency injection for my app? As an example, let's say I have a model that I want to be a singleton. Not a singleton in a self-enforcing getInstance()-type singleton, but a context-enforced singleton (one instance per "context"). I'd like to do something like...

require(['mymodel'], function(mymodel) {
   ...
}

And have mymodel be an instance of the MyModel class. If I were to do this in multiple modules, I would want mymodel to be the same, shared instance.

I have successfully made this work by making the mymodel module like this:

define(function() {
    var MyModel = function() {
        this.value = 10;
    }
    return new MyModel();
});

Is this type of usage expected and common or am I abusing RequireJS? Is there a more appropriate way I can perform dependency injection with RequireJS? Thanks for your help. Still trying to grasp this.

Aaronius
  • 4,936
  • 6
  • 31
  • 40

3 Answers3

60

This is not actually dependency injection, but instead service location: your other modules request a "class" by a string "key," and get back an instance of it that the "service locator" (in this case RequireJS) has been wired to provide for them.

Dependency injection would involve returning the MyModel constructor, i.e. return MyModel, then in a central composition root injecting an instance of MyModel into other instances. I've put together a sample of how this works here: https://gist.github.com/1274607 (also quoted below)

This way the composition root determines whether to hand out a single instance of MyModel (i.e. make it singleton scoped) or new ones for each class that requires it (instance scoped), or something in between. That logic belongs neither in the definition of MyModel, nor in the classes that ask for an instance of it.

(Side note: although I haven't used it, wire.js is a full-fledged dependency injection container for JavaScript that looks pretty cool.)


You are not necessarily abusing RequireJS by using it as you do, although what you are doing seems a bit roundabout, i.e. declaring a class than returning a new instance of it. Why not just do the following?

define(function () {
    var value = 10;

    return {
        doStuff: function () {
            alert(value);
        }
    };
});

The analogy you might be missing is that modules are equivalent to "namespaces" in most other languages, albeit namespaces you can attach functions and values to. (So more like Python than Java or C#.) They are not equivalent to classes, although as you have shown you can make a module's exports equal to those of a given class instance.

So you can create singletons by attaching functions and values directly to the module, but this is kind of like creating a singleton by using a static class: it is highly inflexible and generally not best practice. However, most people do treat their modules as "static classes," because properly architecting a system for dependency injection requires a lot of thought from the outset that is not really the norm in JavaScript.


Here's https://gist.github.com/1274607 inline:

// EntryPoint.js
define(function () {
    return function EntryPoint(model1, model2) {
        // stuff
    };
});

// Model1.js
define(function () {
    return function Model1() {
        // stuff
    };
});

// Model2.js
define(function () {
    return function Model2(helper) {
        // stuff
    };
});

// Helper.js
define(function () {
    return function Helper() {
        // stuff
    };
});

// composition root, probably your main module
define(function (require) {
    var EntryPoint = require("./EntryPoint");
    var Model1 = require("./Model1");
    var Model2 = require("./Model2");
    var Helper = require("./Helper");

    var entryPoint = new EntryPoint(new Model1(), new Model2(new Helper()));
    entryPoint.start();
});
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Domenic
  • 110,262
  • 41
  • 219
  • 271
  • 1
    Well said. Thank you. The reason I used a class, instantiated it, then returned it is because I was thinking about moving the MyModel function/class out to its own JS file and have the mymodel module return an instance. This would make RequireJS seem a **little** more DI container-like. But, you're right, without a composition root it still isn't DI. I appreciate your last paragraph as well. It does make sense. I come from heavy DI-used languages and trying to figure out why it's not commonly used in JS. Does the lack of annotations and static typing just make it overbearing to use? – Aaronius Oct 10 '11 at 14:55
  • 1
    I have started doing DI-style modules recently and found it to be really nice, actually, for many of the same reasons as in static languages (mainly testability and separation of concerns). The lack of interfaces makes things less explicit, but also makes injecting stub instances easier: just pass in an object literal with the appropriate members. So if you can, I'd recommend a DI-type solution, even in JavaScript. People are still figuring out this whole large-scale JavaScript thing, and how modules fit into it all, but I think DI is still a good way to go. – Domenic Oct 10 '11 at 16:42
  • 1
    Mind sharing how you're doing DI-style modules? – Aaronius Oct 10 '11 at 18:19
  • 1
    More or less like the linked gist, although since this is integrating with an older code base, instead of a composition root we have a number of service-located singleton modules that compose a mini object graph inside of them then export appropriately. I've put together a slightly more meaty example, based largely on code inside our actual codebase, at https://gist.github.com/1276149 – Domenic Oct 10 '11 at 18:45
3

If you're serious about DI / IOC, you might be interested in wire.js: https://github.com/cujojs/wire

We use a combination of service relocation (like Domenic describes, but using curl.js instead of RequireJS) and DI (using wire.js). Service relocation comes in very handy when using mock objects in test harnesses. DI seems the best choice for most other use cases.

Just a guy
  • 5,812
  • 4
  • 21
  • 25
unscriptable
  • 766
  • 6
  • 12
2

Not a singleton in a self-enforcing getInstance()-type singleton, but a context-enforced singleton (one instance per "context").

I would recommend it only for static objects. It's perfectly fine to have a static object as a module that you load using in the require/define blocks. You then create a class with only static properties and functions. You then have the equivalent of the Math Object that has constants like PI, E, SQRT and functions like round(), random(), max(), min(). Great for creating Utility classes that can be injected at any time.

Instead of this:

define(function() {
    var MyModel = function() {
        this.value = 10;
    }
    return new MyModel();
});

Which creates an instance, use the pattern for a static object (one where values are always the same as the Object never gets to be instantiated):

define(function() {
    return {
       value: 10
    };
});

or

define(function() {
    var CONSTANT = 10;
    return {
       value: CONSTANT
    };
});

If you want to pass an instance (the result of using a Module that have return new MyModel();), then, within an initialize function, pass a variable that capture the current state / context or pass on the Object that contains information on state / context that your modules needs to know about.

widged
  • 2,749
  • 21
  • 25