How Dependency Injection is meant to work
Dependency Injection or ‘DI’ is a mechanism used within Angular2 to provide instantiated services or other objects to the components (and other services) that need them. It allows a class to ask for just what it needs without needing to know about the entire tree of dependencies in order to create them itself.
The idea of DI is based on design principles intended to help us write better, cleaner and easier to maintain software. Unfortunately, there are certain limitations in the implementation of the Angular2 DI system which mean it’s not always obvious how to get all the benefits we should.
So, we’ll explore these principles using DI with the Http
and an AuthHttp
implementation as an example to show how it is still possible to achieve.
Angular2 provides an out-of-the-box service for making http requests which replaces
the old $resource
module of Angular 2 - just create a class and inject the Http
instance into it and then use that in it’s methods to create the service you need.
Here’s an example:
import { Injectable } from 'angular2/core';
import { Http } from 'angular2/http';
import { config } from '../config';
import { Topic } from '../models';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
@Injectable()
export class TopicService {
constructor(private http:Http) { }
list(subject:string):Observable<Topic[]> {
return this.http
.get(`${config.apiEndpoint}/topics`)
.map(res => res.json());
}
get(id:number):Observable<Topic> {
return this.http
.get(`${config.apiEndpoint}/topics/${id}`)
.map(res => res.json());
}
}
Perfect - we can now inject an instance of TopicService
into any component we
need to list or get Topic
instances through it. All the service needs to use or
know about is the Http
class - that is its dependency. We wire up this dependency
when we add the HTTP_PROVIDERS
to our application bootstrap:
import {Component} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_PROVIDERS} from 'angular2/http';
bootstrap(App, [HTTP_PROVIDERS]);
Suppose we want to add authentication to our app and the server API will check for an Http Authentication header with a Bearer token and use any roles within it to filter the topics to those that should be displayed.
Should the TopicService
need to change to do this? Ideally, no - it wouldn’t. It
would do nothing more with the Http service that it wasn’t doing before and we don’t
want to start adding knowledge of these other orthoganol requirements to it. While we
could add an AuthHttp
class (and most examples will lead us down that path) it can
quickly lead to problems.
First, we’d now be coupled to the AuthHttp
class as well as the Http
class so
couldn’t just use our service elsewhere without pulling that in also (along with
whatever dependencies it uses).
Second, what happens if we want to add new behavior to our Http
request? What if
we also want to log it as well? Do we end up with various combinations of Http
,
AuthHttp
, LoggingHttp
and LoggingAuthHttp
? What happens when we add more?
This is the direction some people go in and what results is tightly coupled, hard to maintain and brittle code.
Fortunately, these issues are well known and good solutions have already been identified and documented as the [SOLID principles of software design](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design))
The important ones in this case are:
a class should have only a single responsibility (i.e. only one potential change in the software’s specification should be able to affect the specification of the class)
Our class should only deal with making Topic API requests, nothing more.
objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
We only need a class that provides us with Http functionality, we don’t care if it is a subclass that also does other things as well (such as adding auth headers or logging).
one should “Depend upon Abstractions. Do not depend upon concretions
Ideally we would depend only on the Http “interface” and not on any concrete implementation detail.
If we follow these principles we can avoid running into some of the problems and keep our codebase clean and maintaintable.
The classic solution to addressing these SOLID principles is to code to an interface and use decorators to wrap the basic implementation with others that add the additional functionality.
So an AuthHttpWrapper adds authenticated token handling but otherwise conforms to the Http interface and can be used in it’s place. Likewise a LoggingHttpWrapper also adds it’s own logging behavior but can also be used in place of the basic Http class.
We can add any number of decorators and to all intents an purposes the code using our wrapped implementation doesn’t need to be aware of them and should be able to see and use the instance as though it was the base Http type.
That’s what we want …
… but there is a problem.
Actually there are two problems. The first is that the type decorator and injection
system used in Angular 2 doesn’t support interfaces. Most DI systems allow you to map
a class to an interface but interfaces kind of evaporate into nothing when they are
transpiled with no distinct DI token so we can’t create our own IHttp
interface to
use in this case.
The second problem is that it’s difficult and unintuitive how to configure the DI system
to make it ignore the previous DI instruction to use the Http
instance that the
HTTP_PROVIDERS configured and use our replacement instead without also reproducing all
of the configuration for the Http dependencies it uses (which is introducing unwanted
coupling again and possibly making our code brittle if those implementations change).
But there is a way (otherwise this would be a pretty unsatifactory blog article!).
The trick is to create a new DI token for the base Http
class and then use the Http
class as the token for the wrapped or decorated instance. This way, components that
ask DI for an instance of Http
will be given the decorated version and the only
thing that needs to know how to get the base instance is the thing that wraps it.
Here’s an example of a wrapper implementation. For this example it simply delegates all
calls to the injected Http
instance but could add functionality to set auth tokens etc…
The key thing is that the Http
instance is injected in but using the special rawHttp
DI token string and the class implements the exact same methods that the Http
class
does so can be used in it’s place.
import {Inject} from 'angular2/core';
import {Request, RequestOptionsArgs, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class AuthHttp {
constructor(@Inject('rawHttp') private http:Http) { }
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return http.request(url, options);
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.http.get(url, options);
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.http.post(url, body, options);
}
put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.http.put(url, body, options);
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.http.delete(url, options);
}
patch(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.http.patch(url, body, options);
}
head(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.http.head(url, options);
}
}
We can then create our own provider to make it easy to add this to an app when it’s required.
This is configuring the Angular 2 Dependency Injection to give an Http
instance when the token
string rawHttp
is requested and to give the wrapped decorator instance when other code asks for
an Http
instance. The existing HTTP_PROVIDERS
code is used to resolve the Http
dependency
so we should not be impacted by any changes to it’s implementation (as long as it’s public
API remains the same).
import {Injector, provide} from 'angular2/core';
import {Http, HTTP_PROVIDERS} from 'angular2/http';
let injector = Injector.resolveAndCreate(HTTP_PROVIDERS);
let http = injector.get(Http);
export const AUTH_HTTP_PROVIDERS: any[] = [
provide('rawHttp', {useValue: http }),
provide(Http, { useClass: AuthHttp })
];
The app bootstrap code would then change to use this AUTH_HTTP_PROVIDERS
instead of HTTP_PROVIDERS
:
import {Component} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {AUTH_HTTP_PROVIDERS} from './services/authhttp.provider';
bootstrap(App, [AUTH_HTTP_PROVIDERS]);
So there we have it. The services that only need to know about the Http
class can continue to ask
for it and not know that they may be given an AuthHttp
class instead.
It would be nice if we could make proper use of interfaces or if the decorating injection functionality could be built into the Angular 2 DI system to make this approach easier.
In the meantime, this works althogh I haven’t tried injecting another level of wrapper yet, I use it for services that are re-used in multiple apps but when not all of them use auth tokens and it’s working well for that.