Skip to content

Instantly share code, notes, and snippets.

@erikringsmuth
Last active March 24, 2017 10:15
Show Gist options
  • Save erikringsmuth/dc7e08e8b8262765c7cc to your computer and use it in GitHub Desktop.
Save erikringsmuth/dc7e08e8b8262765c7cc to your computer and use it in GitHub Desktop.
JavaScript DI and Modules

Here's a more in depth explanation of Angular's DI, RequireJS, and what's coming up in Angular 2.0 and ES6 modules.

Angular DI is not a module loader! It injects objects that have already been loaded using script tags. It does not do lazy-loading. Angular 2.0 will use ES6 import for module loading. Angular 2.0 will also add a DI layer that runs after the modules load.

I need to go over Java's import vs. JavaScript's require/import and Java singletons to further explain what Angular's DI is doing.

Java's import allows you to access another package's types without fully qualifying the package name. JavaScript's import loads a module and creates references to the module's exported objects. These are very different operations. JavaScript's import is powerful stuff! You have the objects, not just aliases to a package's types.

Singleton is a pattern, not the object itself. In Java you start with a type and have to create an instance. The singleton pattern ensures you have only 1 instance of that type. Java can use DI to register a type as a singleton. The dependency injection will ensure that the same instance is injected in every class.

In JavaScript you start with an object. Don't use new if you only want 1 object. Rather than using DI to inject an instance of a constructor function you can import a plain-old object.

In Java you can use DI to @Inject member variables. DI's most popular use is injecting mock objects when unit testing. DI can do so much more than inject mocks but that's the extend most people use it for.

In JavaScript you can use a module loader helper to import a mock object when unit testing. You don't need DI to inject a mock. It's complete overkill.

Angular 1.0 doesn't have a module loader. It uses script tags <script src="/js/myAngularModule.js"></script>. An Angular "module" registers itself as a service, factory, or provider in Angular's IoC container by specifying a module name. This is a type of namespacing and can easily lead to collisions. When an Angular module needs to reference another Angular module it uses Angular's DI to inject a reference. A factory returns a single object and a service returns a new instance every time. Angular's DI also handles injecting mocks when unit testing.

Angular 2.0 is keeping the DI concept. This time they're using the ES6 module loader to get a reference then they add a constructor injection step to inject a new instance.

import {MyService} from './myService';

@Inject(MyService)
export class MyThing {
  constructor(myService) {
    this.myService = myService;
  }

  someAction() {
    this.myService; // the injected instance
  }
}

The DI is calling new MyService() for you and making you do the extra constructor injection work.

With Angular 2.0 you're already using the ES6 module loader that returns an object. You can optionally skip the DI step if your service exports an object instead of a constructor function or ES6 class.

import {myService} from './myService';

export class MyThing {
  someAction() {
    myService; // it's just an object
  }
}

When you want to test MyThing you use Jest, injectr, Squire, or one of the many helpers that load a mock instead of the real myService. It's that simple. This is all most people want to do 99% of the time when using DI.

There is a time and place for DI. It can wire up more complex objects as show here https://gist.github.com/domenic/5753428. On the other hand, the module loading pattern is simple and you see exactly what's happening. Mocking is just as easy.

@royriojas
Copy link

I really believe that if you're using CommonJS modules already (with Browserify for example) you don't really need a DI container.

In order to prove it, I have rewritten the Coffee Maker example found in this video https://www.youtube.com/watch?v=_OGGsf1ZXMs#t=121

https://github.com/royriojas/coffe-maker

Basically you can just use the injectr approach of provide a second parameter to the require function.
This second parameter is the list of modules to be mocked when required inside the test. This means require is tampered in your testing environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment