Techno Fattie

Josh Carroll

Angular is Awesome!

If you aren't already familiar with Angular.js, then you really owe it to yourself to dig into this amazing framework. Misko Hevery (@mhevery) and his team have done an outstanding job, and I continue to be delighted by just how well designed and flexible the framework is.

Dependency Injection

At the heart of this flexibility is the Dependency Injection capabilities that Angular provides. However, while this is arguably the most powerful feature in Angular, it can also seem like a bit of voodoo magic at times. You may be wondering just how, or when, you can use this to your advantage. I'm going to try and shine some light on this subject by showing you the most basic examples of dependency injection in Angular. Let's imagine we had a simple service like this, and a very simple module that register the service.

function simpleService(){
   this.name = "simpleService";
}

angular.module('foo', [])
   .service('simpleService', simpleService);

Using The Injector

Now imagine we wanted to manually grab an instance of that service (which you should almost never do) and use it. To do this we can create a new injector and let it do the magic of creating the service for us.

var myInjector = angular.injector(['foo']);
var service = myInjector.get('simpleService');

console.log(service.name); // 'simpleService'

So that's it... the simplest possible case. But what happens if you have two modules with the same service name defined? What happens then? Well, this is where it starts to get interesting. Let's look at two more examples.

angular.module('foo', [])
   .service('simpleService', function(){ this.name = "foo"; });

angular.module('bar', [])
   .service('simpleService', function(){ this.name = "bar"; });

var fooSvc = angular.injector(['foo']).get('simpleService');
var barSvc = angular.injector(['bar']).get('simpleService');

console.log(fooSvc.name); // 'foo'
console.log(barSvc.name); // 'bar'

Module Ordering

This is more or less what you would expect to see. Each injector references only a single module, and therefore the services are isolated. But can't you create an injector that references more than one module? What happens then?

var fooSvc = angular.injector(['foo','bar']).get('simpleService');
var barSvc = angular.injector(['bar','foo']).get('simpleService');

console.log(fooSvc.name); // 'bar'
console.log(barSvc.name); // 'foo'

Did you see what happened there? Basically the last module to execute and register it's services won. It's simply a matter of the order in which they get executed. But what if one our modules has a dependency? How does that change things?

angular.module('foo', [])
   .service('simpleService', function(){ this.name = "foo"; });

angular.module('bar', ['foo'])
   .service('simpleService', function(){ this.name = "bar"; });

var fooSvc = angular.injector(['foo','bar']).get('simpleService');
var barSvc = angular.injector(['bar','foo']).get('simpleService');

console.log(fooSvc.name); // 'bar'
console.log(barSvc.name); // 'bar'

Module Dependencies

Again, it's all about the order in which they execute, but in this particular scenario, because our bar module has a dependency on the foo module, foo will always get invoked first. This is where you can start to take advantage of this system to change how services operate, or replace them wholesale. Imagine we were testing, and wanted to replace the $httpBackend with a custom one, or a mock implementation that we can precisely control? This becomes trivially easy to do because of how Angular is designed. All you would have to do is create a module that is dependent on the ng module, and override any service you wanted to. Indeed this is exactly how the ngMock module works.

Service Interception

Now, replacing a service is great, but one especially nice feature in Angular is the ability to intercept a service just after it has been created. This can be achieved easily by using a decorator.

angular.module('foo', [])
   .service('simpleService', function(){ this.name = "foo"; });

angular.module('bar', ['foo'])
   .config(function($provide){

      //$provide was injected for me automatically by Angular
      $provide.decorator('simpleService', function($delegate){

         //$delegate is the service instance, and is
         // also injected automatically by Angular
         $delegate.name += "|bar";

  return $delegate;
      });
   });

var fooSvc = angular.injector(['foo']).get('simpleService');
var barSvc = angular.injector(['bar']).get('simpleService');

console.log(fooSvc.name); // 'foo'
console.log(barSvc.name); // 'foo|bar'

Conclusion

I won't go into all the gory details on this, but basically we are able to intercept the simpleService from foo and do something with it before returning. You can check out the documentation on both the config and the decorator methods. I hope this sheds a little light on modules, services and dependency injection in Angular. The ability to replace and intercept services opens up a whole world of possibilities and makes testing much easier.

comments powered by Disqus