In this article I will cover some of the details of Angular dependency injection and highlight some of the changes from Angular 1.x.
At this point most developers are familiar with dependency injection in some form, so the concepts described in this article should be familiar to most. Angular doesn't really do anything out of the ordinary when it comes to DI – beyond introducing new syntax, of course :-).
The main goal of DI is to avoid tightly coupled components by injecting dependencies rather than instantiating them directly in consuming components. The advantages of this might not be obvious at first, but the decoupling makes your components much more flexible since you can “inject” different implementations to the same component – provided the implementations conform to a standard contract. A typical application of this is unit testing since DI allows you to pass in a mock implementation without requiring changes to the components under test.
In the following example I will show how to create a simple address book component where various dependencies are injected into the main component.
There is a lot of flexibility when it comes to writing Angular code, but I have decided to go in the TypeScript direction, so all my code samples will be using TypeScript.
The beauty of using TypeScript is that a lot of the ceremony related to DI is handled by the compiler.
Let's start with the top level component - AddressBook
As you can tell I am injecting two dependencies in the constructor of the component - addressBookService and addressBookTitleService.
Both services are plain classes as seen below:
As with all DI frameworks we have to inform Angular that these are dependencies we want to inject “somewhere”. The way we do that in Angular is by specifying “providers” - either at the component level or at the app level during bootstrapping.
In this case I've decided to specify a binding for AddressBookService at the component level:
For illustration purposes I wired up the binding for AddressBookTitleService at the app level by passing in an array to the bootstrap method.
One of these methods of registering the dependency is necessary in order to notify Angular that the classes can be injected as dependencies.
I did however notice one important difference between the two approaches. Registering the provider at the app level creates the injected dependency as a singleton for the entire application, but providers at the component level result in new instances at the component level. Objects are created new for every injection per component, so you can't use them to store state at the application level, but the hierarchy within the component still share the same instance. I guess you can view it as a local, component level, "Singleton". This is different from Angular 1.x where you only have pure singleton dependencies.
I have added log statement in the constructors above, so that you can see the singleton vs transient object behavior.
Another thing to point out is the @Injectable() marker on AddressBookService. This is a bit odd, but is necessary to handle the entire dependency chain from AddressBook down to the dependencies of AddressBookService itself. Basically anytime you have nested dependencies you have to add the @Injectable() marker in order to satisfy the TypeScript compiler.