Angular2 DI Service Decorator

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.

Http Service Dependency Example

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]);

SOLID Principles

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:

Single responsibility principle

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.

Liskov substitution principle

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

Dependency inversion principle

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 Solution

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 …

The Problem with the Solution

… 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).

The Solution to the Problem with the Solution

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.

comments powered by Disqus