My Angular 2 blogging series continues and today’s I’ll present you a high level overview of Dependency Injection (DI) in Angular 2.
Any Angular 2 application is a collection of components, directives, and classes that may depend on each other. While each component can explicitly instantiate its dependencies, Angular can do this job for you by using its Dependency Injection mechanism.
I’ll explain you how Angular 2 does it, but first let’s make sure that we are not confusing DI as a design pattern vs a specific DI implementation as the same design patterns can be implemented differently depending on the software you use. For example, in the Java world there are multiple implementations of DI. For example, Spring Framework has the most popular DI container. Java EE has its own implementation of Resource Injection and Context Dependency Injection. Angular 2 has its own implementation of the DI pattern.
DI and IoC Patterns
Imagine a fulfillment center that ships products. An application that keeps track of shipped products may create a product object and invoke a function that creates and saves a shipment record:
var product = new Product(); createShipment(product);
The function createShipment() depends on the existence of an instance of the Product object. In other words, the function createShipment() has a dependency: Product. The function itself doesn’t know how to create it. The calling script should somehow create and inject this object as an argument to the function createShipment().
But the thing is that the class Product should be implemented by the offshore team, and it’s not ready yet (oh, those Indians!). So you decided to provide a MockProduct as a substitute for an actual Product until the offshore team catches up.
In our two-line code sample it’s an easy change, but what if the function createShipment() has three dependencies (e.g. product, shipping company, and fulfillment center) and each of these dependencies has it’s own dependencies? Now creating a different set of mock objects for createShipment() would need a lot more manual code changes. Would it be possible to ask someone to create instances of dependencies (with their dependencies) for us?
This is what the Dependency Injection pattern is about: if an object A depends on the object of type B, the object A won’t explicitly instantiate the object B (as we did with the new operator above), but rather will get it injected from the operational environment. In other words, the object A just needs to declare, “I need an object of type B, could someone please give it to me?” The words of type are important here. The object A does not request a specific implementation of the object and will be happy as long as the injected object is of type B.
The Inversion of Control (IoC) is a more general pattern than DI. Rather than making your application to use some API from a framework (or a software container), the framework will create and supply the object(s) that the application needs. The IoC pattern can be implemented in different ways and DI is one of the ways to provide required objects. Angular plays a role of the IoC container and can provide the required objects according to your component’s declarations.
Benefits of DI and how Angular Does It
Angular offers a mechanism that helps registering and instantiating component dependencies. In short, DI helps in writing code in a loosely coupled way and makes your code more testable and reusable.
Loose coupling and reusability
Say you have a ProductComponent that gets product details using the ProductService class. Without DI your ProductComponent needs to know how to instantiate the class ProductService. It can be done by multiple ways, e.g. using new, calling getInstance() on some singleton object, or maybe invoking a createProductService() on some factory class. In any case ProductComponent becomes tightly coupled with ProductService.
If you need to reuse the ProductComponent in another application that uses different service for getting product details, you’d need to modify the code (e.g. productService = new AnotherProductService()). DI allows to decouple application components by sparing them from the need to know how to create their dependencies. Consider the following ProductComponent sample:
@Component({ providers: [ProductService] }) class ProductComponent { product: Product; constructor(productService: ProductService) { this.product = productService.getProduct(); } }
In Angular applications written in TypeScript you do this:
a) Register an object(s) for DI by specifying a provider(s), which is an instruction to Angular on how to instantiate this object when the injection is requested
b) Declare a class with a constructor that has the argument(s) of types that match provider’s types
When Angular sees a constructor with an argument of a particular type, it starts looking for a provider that specifies HOW to instantiate an object of this type. If the provider’s found in the current component, Angular will use it to instantiate an object and inject it to the component via its constructor.
Angular 2 inject object only via constructor’s argument, which greatly simplifies this process comparing to Angular 1.
In the above code snippet the line providers:[ProductService] is a shortcut for provide(ProductService, {useClass:ProductService}). We’re instructing Angular to provide a token ProductService using the class of same name. Using the method provide() you can map the same token to a different class (e.g. to emulate the functionality of the ProductService while someone else is developing a real service class).
Now that we’ve added the providers property to the @Component annotation of the ProductComponent, Angular’s DI module will know that it has to instantiate the object of type ProductService.
The ProductComponent doesn’t need to know which concrete implementation of the ProductService type to use – it’ll use whatever object was specified as a provider. The reference to the ProductService object will be injected via the constructor’s argument, and there is no need to explicitly instantiate ProductService inside ProductComponent. Just use it as in the above code, which calls the service method getDetails() on the ProductService instance magically created by Angular.
If you need to reuse the same ProductComponent in a different application with a different implementation of the type ProductService, change the providers line, for example:
providers: [provide(ProductService, {useClass:AnotherProductService})]
Now Angular will instantiate AnotherProductService, but your code that was using the type ProductService won’t break. In our example using DI increases reusability of the ProductComponent and eliminates its tight coupling with ProductService. If one object is tightly-coupled with another, this may require substantial code modifications should you want to reuse just one of them in another application.
Testability
DI increases testability of your components in isolation. You can easily inject mock objects where if their real implementations are not available or you want to unit-test your code. Say you need to add a login feature to your application. You can create a LoginComponent (it would render ID/password fields) that uses a LoginService component, which should connect to a certain authorization server and check the user’s privileges. The authorization server has to be provided by a different department (oh, those Russians), but it’s not ready yet.
You finished coding the LoginComponent, but you can’t test it for reasons that are out of your control, i.e. dependency on another component developed by someone else.
In testing we often use mock objects, which mimic the behavior of real objects. With a DI framework you can create a mock object MockLoginService that doesn’t actually connect to authorization server but rather has hard-coded access privileges assigned to the users with certain ID/password combinations. Using DI you can can write a single line injecting the MockLoginService into your application’s Login view without the need to wait till the authorization server is ready.
Later on, when that server is ready you can modify the providers line so Angular would inject the real LoginService component as shown below.
Injectors and Providers
Angular 2 has such concepts as injectors and providers. Each component has an Injector capable of injecting objects into other components. Any Angular application is hosted by some root component, so you always have a root injector available. An injector knows how to inject one object of the specified type into the constructor of another.
Providers allows you to map a custom type (or a string) to a concrete implementation of this type (or a value). We’ll be using ProductComponent and ProductService for all code samples in this chapter, and if your application has only one implementation of a particular type (i.e. ProductService), specifying a provider can look like this:
provide(ProductService,{useClass:ProductService});
IMPORTANT: The providers property is specified inside the @Component annotation, which allows Angular to register the class for future injection. No instance of the ProductService is created at this point. The providers line instructs the injector as follows: “When you’ll need to construct an object that has an argument of type ProductService create an instance of the registered class for injection into this object”.
The shorter notation of the above provider statement looks like this:
providers:[ProductService]
If you need to inject a different implementation of a particular type, use the longer notation:
provide(ProductService, {useClass: MockProductService});
Now we’re giving the following instruction to the injector:
“When you’ll need to inject an object of type ProductService into a constructor’s argument create an instance of the class MockProductService”.
The injector knows what to inject, and now we need to specify where to inject the object. In TypeScript it comes down to declaring a constructor argument specifying its type. The following line shows how to inject the object of type ProductService into the constructor of some component.
constructor(productService: ProductService)
The constructor will remain the same regardless of which concrete implementation of ProductService was specified as a provider. Below is a sample sequence diagram of the injecting process.
Injection With TypeScript vs ES6
TypeScript simplifies the syntax of injection as it doesn’t require using any DI annotations. Specifying the type of the constructor’s argument is all that’s needed:
constructor(productService: ProductService)
There is one requirement for the above line to work though: the TypeScript compiler need to be configured with the option “emitDecoratorMetadata”: true. With this option the generated JavaScript code will contain the metadata information about the argument type so Angular will know which type of the object to inject.
I use the SystemJS polyfill for on-the-fly TypeScript transpiling, we can add the following TypeScript compiler’s option in the configuration file for SystemJS:
typescriptOptions: { "emitDecoratorMetadata": true }
If you’d be writing the class in ES6, its constructor would need the @Inject annotation with explicit type:
constructor(@Inject(ProductService) productService)
How to Create a Provider
The function provide() returns an instance of the Provider object, and this function can be called in different ways because the object creator can be a class or a factory (with or without dependencies).
* To map a type to an instance of a class use the function provide() where the second argument is an object with the useClass property.
* If you have a factory function that instantiates objects based on certain criteria use the function provide(), where the second argument is an object with the useFactory property, which specifies a factory function (or a fat arrow expression) that knows how to instantiate required objects. The factory function can have an optional argument with dependencies, if any.
* To provide a string with simple injectable value (e.g. a URL of a service ) use the function provide() where the second argument is an object with the useValue property.
In the next blogs I’ll write a sample app to illustrate the use of useClass, useFactory, and useValue.
I’m co-authoring the book “Angular 2 Development with TypeScript“, and Manning offers the electronic version of whatever content is ready (138 pages are available). Starting on February 28, we’ll also teach an online class on Angular 2, and you can use the promo code blogreader to get $50 off the price.
3 thoughts on “Getting familiar with Angular 2 Dependency Injection”