Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection with RequireJS

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.

like image 505
Aaronius Avatar asked Oct 10 '11 03:10

Aaronius


People also ask

What is RequireJS used for?

RequireJS is a JavaScript library and file loader which manages the dependencies between JavaScript files and in modular programming. It also helps to improve the speed and quality of the code.

Do I need RequireJS?

Generally you only use RequireJS in its loading form during development. Once the site is done and ready for deployment, you minify the code. The advantage here is RequireJS knows exactly what your dependencies are, and thus can easily minify the code in the correct order.

What is RequireJS Shim?

As per RequireJS API documentation, shim lets you. Configure the dependencies, exports, and custom initialization for older, traditional "browser globals" scripts that do not use define() to declare the dependencies and set a module value.


1 Answers

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(); }); 
like image 169
Domenic Avatar answered Oct 06 '22 08:10

Domenic