2

I'm studying Dependency Injection in JavaScript. I've learned what I need to accomplish, according to Wikipedia,

The client delegates to external code (the injector) the responsibility of providing its dependencies. The client is not allowed to call the injector code.[2] It is the injecting code that constructs the services and calls the client to inject them. This means the client code does not need to know about the injecting code. The client does not need to know how to construct the services. The client does not need to know which actual services it is using. The client only needs to know about the intrinsic interfaces of the services because these define how the client may use the services.

But I'm not sure how this should be done in JavaScript. I could probably set out to create all sorts of ways to inject dependencies in JavaScript, matching "services" to "clients" in the code structure and technically satisfy the terms of the Dependency Inversion Principle. But that doesn't mean it would be the best/correct way.

For example lets say I have a wheel (service) which every car (client) needs, along with services: gasPedal, break, clutch, and stick.

  • What should a proper dependency injector look like in JavaScript?
  • How does one inject the dependency without calling on the injection code from the client?

Note: None of the answers so far cover doing this manually. I know I can use libraries to do it, what I'm asking about - the challenging aspect of injectors - is how to create them from scratch, what the structure of one would actually look like, not how to use a library which supports them.

I'm trying to learn what the function for an injector would be structured like for the sake of truly understanding dependency injection at its core - the function that implements injection. I'm not asking for a complete function or working code, just a good explanation or even an overly simplified pseudo-code example.

4
  • How about a call to a method that passes a parameter that determines what the method does ? Sep 25, 2015 at 14:53
  • Note: I edited steer to wheel since a service is defined as an object that does something, so the dependency would be wheel and what it does would be steer()
    – J.Todd
    Sep 25, 2015 at 14:57
  • You should look at the Angular source code. Angular does JavaScript dependency injection.
    – Mohair
    Sep 25, 2015 at 14:59
  • This doesn't feel like a duplicate to me. Dependency Injection is not an OO principle and doesn't make sense in that context. It is not a generalization, it is a composition relationship between the two. It would be like saying "describe properties of a racecar driver" is a duplicate of "describe properties of an automobile". With that being said, please be civil. If something doesn't look right then please flag for a moderator. Thank you.
    – maple_shaft
    Sep 25, 2015 at 18:12

2 Answers 2

4

How it "should" be done is a subject of debate, but I can provide you an example that works well from a pretty common library -- angularjs:

(function() { // wrap in anonymous function to not pollute global scope
    angular
        .module('mymodule') // which module to register this object with
        // register 'someSvc' identifier to inject someSvc object
        // for testing, provide a replacement object
        .service('someSvc', someSvc);

    // the object that will be injected
    function someSvc() {
        var privateVar = '';
        this.publicVar = '';
        this.publicMethod = function() { ... };
    }
})();

When I later use someSvc in another component (which will also be registered with the dependency injector)...:

(function() { // wrap in anonymous function to not pollute global scope
    angular
        .module('mymodule') // which module to register this object with
        // register someCtrl object with identifier 'someCtrl'
        .controller('someCtrl', someCtrl);

    // tell angular which dependencies this object requires
    // this method of injection survives javascript minification
    someCtrl.$inject = ['someSvc']

    // constructor inject dependencies
    // name someSvc doesn't have to match (since we used $inject above)
    // but kept same for clarity
    function someCtrl(someSvc) {
        this.doSomething = function () {
            someSvc.publicMethod();
        }
    }
    // javascript functions are "hoisted", so it's available even if declared last
})();

Scopes

There are several registration methods on angular which essentially provide different scopes like a standard DI container. .service news up the object only once (singleton). .factory expects a function which returns the object and only calls said function once (singleton). .controller is per object lifetime scope and generally used for page template controllers.

JS Minification

You must also keep in mind that variable names can and will be changed by minification process, so your method of injection must still work when variable names change. In angular, this is accomplished by specifying the string identifier of the dependency (via $inject or array syntax on the module registration).

Injection

The above syntax registers dependencies with the injector. The dependencies are injected by the injector like so:

// create an injector
var $injector = angular.injector(['ng']);

// use the injector to kick off your application
// use the type inference to auto inject arguments, or use implicit injection
$injector.invoke(function($rootScope, $compile, $document) {
  $compile($document)($rootScope);
  $rootScope.$digest();
});

Example pulled from this link. The injector reads the parameter names (or the $inject property) of the invoked function and tries to find matching dependency names in its registry. If it cannot, then it crashes. Obviously, it is your responsibility to make sure the appropriate modules are registered before calling the injector. (Angular automatically calls the injector during it's startup process as it scans and finds registered components specified in the HTML DOM.)

More information on angular DI can be found here.

4
  • What is the importance of .service() only making the injected function available for one call?
    – J.Todd
    Sep 25, 2015 at 21:11
  • And sorry but I'm really interested in someone explaining how an injector function actually works (not a library syntax for a DI function), for example how the injector isn't triggered by the client - I'm having trouble figuring that out.
    – J.Todd
    Sep 25, 2015 at 21:38
  • Please review DI object lifetimes. In angular, .service is a singleton, which means it's only created once, and that same instance is given to every component that requires it. Whereas .controller objects are created new every time they are injected. Sep 26, 2015 at 1:19
  • I'll add some more info about how it works. Sep 26, 2015 at 1:20
1

There are a number of existing solutions you should look at before reinventing the wheel. One is Asynchronous Module Definition (AMD), which is shared by a few frameworks and usable by several others. Pretty much all frameworks of any note have solved this problem.

The big difference with JavaScript compared to other languages is the idea that you usually want to be able to load dependencies asynchronously over the web on demand, and only those you need, and cache it for later use. On the other hand, you also want the ability to sort of compile all your dependencies into one minified file that is quick to download for production use, but have that version be semantically identical to the asynchronous on-demand version you used during development.

1
  • I didn't even consider the idea of starting modules the moment their dependencies where loaded rather than once the scripts were all loaded. A big +1 for speed consideration, but I'm still interested in seeing how an Injector is done properly without consideration for starting before all the scripts are in. Thats a complex challenge, I'm just looking for how to do this in a single JS file on a small fundamental level.
    – J.Todd
    Sep 25, 2015 at 15:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.