3

I've been looking into dependency injection, what it is, how it works, how it's being used. It's a neat system and to understand it a bit better, I'm going to implement a small demo app using this system.

I primarily write in JavaScript so it comes natural to look for inspiration in the same ecosystem. Unfortunately, there's not a whole lot of resources about DI in JavaScript. DI doesn't appear to be a thing in this language.

But if you look at other languages:

  • In Java, you can see DI in Spring.
  • In PHP, you can see DI in Symfony, Laravel, Drupal.
  • In TypeScript, you can see DI in Angular (2.x+).

There's great emphasis on DI on their popular frameworks and tools. When you dive into them, you'll immediately be greeted with things like Services and Components and Plugins and Annotations, all that fun stuff - but not in JS.

The closest you have to DI in JS is AngularJS (1.x), but that's about it. Other mainstream frameworks don't seem to use it. And if any tools or libraries exist, they're not mainstream either.

Is there something in a language that makes DI practical/not practical to implement? Is it something in the language's features that need to be there in order to do DI? Is it the type of app being built (i.e. data-ish vs UI-ish)? Or are JS frameworks and libraries actually using DI, just not in a mode I'm familiar with?

6
  • I guess the question is does javascript support interfaces (or a similar construct)
    – Robbie Dee
    Jan 22, 2019 at 16:17
  • 3
    It might help to understand what problem you would want to use DI to solve in the context of Javascript.
    – JimmyJames
    Jan 22, 2019 at 17:32
  • @JimmyJames There's no specific problem to solve right now. I'm just learning how DI patterns and frameworks work by implementing them in a small app. It just happens that I use JS where DI isn't a thing, hence the question.
    – Joseph
    Jan 22, 2019 at 18:23
  • @Joseph I think part of that is the context in which Javascript was designed for and mainly used. In a browser, you are injecting all the code in the browser, in a way.
    – JimmyJames
    Jan 22, 2019 at 18:32
  • 2
    @JohnZabroski To which the answer is evidently and ostensibly yes...
    – Robbie Dee
    Jan 23, 2019 at 8:39

3 Answers 3

13

Dependency injection just means passing dependencies as parameters and you can do this fine in JavaScript. But dependency injection frameworks are not a good fit for a language like JavaScript without classes or interfaces.

A DI framework typically manages what dependencies to inject by looking at the signature of the constructor and then find or instantiate classes which correspond to the parameter types. Problem is, JavaScript does not have interfaces or class types, so there is no straightforward way to indicate what the dependencies are.

7
  • 2
    I would disagree that DI frameworks aren't useful for JavaScript applications. Just think of RequireJs. It isn't dependency injection in the same sense as C# or Java, but it does load dependencies and invoke a callback while passing that dependency in to the callback function. Absent classes and interfaces for DI, you can use configuration or naming conventions. I also wrote a DI framework for JavaScript called Hypodermic and don't forget AngularJs DI. Jan 22, 2019 at 19:52
  • 2
    At least since ECMAScript 2015 there is a standardized syntax for classes. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Referencing classes is possible by just writing the class name (which turns out to be the constructor function itself). By providing the desired types (the container my choose specialized variants) as metadata (using a simple static field in the class - MyClass.inject = [Type1, Type2]; or the not yet fully standardized decorators - github.com/tc39/proposal-decorators/blob/master/README.md) you can probably build a DI framework. Jan 22, 2019 at 20:15
  • 1
    @Greg Burghardt I have yet to meet anyone who can write a RequireJS configuration right, the first time, for any reasonably complex application . In some cases, it seems impossible to write one correctly, regardless of what shim you use. Jan 22, 2019 at 22:45
  • @HeroWanders: Without interfaces you miss the primary reason for DI in the first place, which is to depend on interfaces rater than implementations.
    – JacquesB
    Jan 23, 2019 at 10:50
  • 2
    @JacquesB (and Hero Wanderers): Being that JavaScript is about as strongly typed as cooked spaghetti is rigid, you don't need interfaces. Successful use of DI in JavaScript (and use of polymorphism) hinges on not caring about the type, and just using the object. Provide documentation about the "interface" it uses and leave it up to the developer to get it right. Either that or use TypeScript, but that comes with its own challenges too. The C#, Java and C++ programmers forget how spoiled we are with compilers and syntax checkers. Jan 23, 2019 at 12:23
4

Dependency injection is a very object-oriented approach: you define some interface or protocol and then at runtime dynamically provide some object that provides this interface. This makes it possible to run the same software in vastly different configurations.

This is in stark contrast e.g. to abstract data types that may also have a common interface, but are selected before run time. For example, a C program may use preprocessor directives to conditionally include different implementations of some interface. But that is not DI.

Since JavaScript is an object-oriented language (in the sense that it has built-in support for dynamic method dispatch) you can totally use DI.

However, as its core “Dependency Injection” is just a fancy word for “passing parameters to functions”. That can be done manually by passing all dependencies around explicitly. Some creational design patterns like the Abstract Factory Pattern can also help with managing all dependencies. That is certainly possible in JavaScript, since it is just a matter of software design.

But what people think about most of the time when they hear Dependency Injection is a Dependency Injection Framework or Container. These typically provide some shortcuts that make it unnecessary to handle dependencies manually (i.e. the DI framework implements part of the constructor) and they often have a global registry that maps dependency interfaces to implementations.

In statically typed languages this registry may use the interface types themselves as registry keys. This is not really possible in JavaScript, especially since the language has no comparable concept of interfaces. Instead, you would have to provide your own naming scheme for the registry. At that point the registry is indistinguishable from an ordinary JavaScript object that looks a bit like an abstract factory. Some frameworks may provide their own registry, but DI is not so common that the same registry mechanism would be pervasively used by the whole ecosystem.

3

In short, yes, the mathematical features of your programming language will make a huge difference in how you use dependency injection. I'll try to motivate why language choices matter in making optimal use of dependency injection, and also motivate complex dependency injection with a basic look into how to implement a plug-in architecture, and how we can use

Software engineers mainly talk about design patterns like Resource Acquisition Is Initialization, Service Locator, and Abstract Factory.

Mathematicians use phrases like universal quantification and existential quantification. So, as I explain plug-in architecture, I'll try to tie back "mathy" concepts with DI.

I'll start by explaining what Service Locator and Factory Method are, structurally, by quoting Mark Seeman and describing their type signatures:

An Abstract Factory is a generic type, and the return type of the Create method is determined by the type of the factory itself. In other words, a constructed type can only return instances of a single type.

A Service Locator, on the other hand, is a non-generic interface with a generic method. The Create method of a single Service Locator can return instances of an infinite number of types.

Service Locator

public interface IServiceLocator
{
  T Create<T>(object context);
}

Abstract Factory

public interface IFactory<T>
{
  T Create(object context);
}

Or, in the degenerative case of no parameters:

Service Locator

public interface IServiceLocator
{
  T Create<T>();
}

Abstract Factory

public interface IFactory<T>
{
  T Create();
}

In a language without generic types, like JavaScript, there is literally no difference between the two signatures. The only detail that then matters is the name given to the interface. This is a shame, because you can't describe at code write time exactly how you intend for your architecture to work.

OK, so why is it qualitatively bad to only use names? After all, many programmers prefer dynamically typed languages because they're lightweight and have very few rules. Why would that same group of programmers care about only using names for dependency injection instead of types? Why would not being able to tell the difference between Service Locator and Abstract Factory matter any more than whether a number is an integer or a decimal or a floating point number? After all, these DI design patterns are just extra types, how does using Javascript actually make programming harder?

The biggest problem with only using names can be seen in how Javascript frameworks like Angular.js implemented dependency injection: they use hierarchical search. The main reason Angular developers encourage this is because there is no other way to provide a scope for name resolution.

As an experienced engineer who understands DI already, I hate having to read all the non-mathematical descriptions in the Angular.js DI docs. It just wastes my time, takes a long time to read, and is full of idiosyncratic techniques. I'd rather just learn generic programming with types, once, and apply that knowledge to any language I have to use for work.

Even the example for Angular.js service isolation is contrived: because they don't have type safety, they require every view component to explicitly pre-define, by typing out "providers", in the component list. You have to type it out, because there are no types to save you from having to type it out.

ES7 gets JavaScript developers a bit closer thanks to TypeScript and ES7 Metadata. One example of an IoC library in JavaScript is Inversify. In effect, TypeScript (a typed language) compiles down to JavaScript and creates a mapping layer that looks at ES7 metadata in order to resolve types. This scraps some of the boilerplate associated with JavaScript frameworks relying upon lexical names rather than types.

There's more I want to write here, but please write in the comments if I'm speaking to you at your level of understanding. We have not even covered yet more functional approaches to dependency injection and common functional design patterns such as Continuation Passing Style, which is the functional way to invert dependencies to the user and allow the user to use your library in ways you never imagined. There is also the trick of encoding existentials, by describing existentials in terms of the universals which can be used to operate upon them. using crates: if your type system does not directly support saying "For all types x, such that…", how can you still say it? The trick is to avoid passing around the function directly. You let consumers pick the return type using their own universally quantified function. In some way, this is what C# DI frameworks like DryIoC allow you to achieve, just in an OO style.

I have one last point about OO DI. Most frameworks just expose interface composition, rather than functional composition. What I mean is that you can say this:

public interface IHas<T> { T MyT { get; set; } } 
public class FooV1 {
    T MyT { get; set; } 
} 
public class FooV2 : IHas<T> {
    T MyT { get; set; } 
    FooV2(T myT) {
       MyT = myT;
    } 
}

It would be ideal if I could just use FooV1 and automatically inject a T, to avoid having to type things 4 times in a row:

  1. In the interface definition
  2. In the class definition describing that it implements the interface IHas
  3. In the constructor describing that you need a T dependency to create this object
  4. Assigning the external dependency from the constructor argument myT to the class property MyT.

I would prefer DI frameworks that can automatically detect I want a T, and give it to me, to those that require I create extra boilerplate everywhere. I would say the boilerplate approach uses interface composition, whereas I prefer function composition.

I'll leave you with a simple quote of how many functional programmers go wrong, which is eerily similar to how Javascript programmers go wrong:

Eirik Tsarpalis (@eiriktsarpalis) tweeted at 2:48 PM on Thu, Feb 15, 2018: F# modules are essentially static classes that cannot be parameterized on the module level. As such the tendency is to include singletons or expose all dependencies as function parameters. Often they combine both approaches. It makes for highly untestable code. (https://twitter.com/eiriktsarpalis/status/964134272658485248?s=03)

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.