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.
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 therequire
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.