AngularJS 1.x and TypeScript: The biggest tutorial, pt. 2 - Dependency Injection

No much time has passed since my last post but I couldn't resist. I know that first article of any series introduces a lot of things so there's no time to write everything in details. The last article was about the basics of the basics. Today we are going to go one step further. The AngularJS' Dependency Injection.

I won't write much about AngularJS' DI mechanism because it follows basic principles of each DI container with one restriction - each element has to be declared with angular lib. It's nothing special, every framework I know does it in similar way - Symfony2 need some metadata with services that can be injected or Java with beans configuration. In angular we can inject services (declared as service or factory) as well as values, contants, etc. Unlike already mentioned Symfony2 or Java, configuration isn't done by configuration files but with angular providers which are invoked before the service is initialized or injected into the container. Similar approach is implemented in Silex micro-framework, which uses Symfony2 components. Angular's providers can be also used separately as standard injectables.

Ok, that's enough for DI introduction. you may wonder how to inject other services into your own service or directive or anything else, using still POJO approach that you know from previous article, with TypeScript. We'll get through this step-by-step to avoid your confusion.

Re-introduction to DI in AngularJS

So, let's take a look at service declaration in JavaScript injecting other services. Probably you do it in the following way:

angular  
    .module('myModule')
    .service('Greeter', ['$q', function ($q) {
        this.greetWithTimeout = function (name) {
            var deferred = $q.defer();
            setTimeout(function () {
                deferred.resolve("Hello, " + name);
            }, 5000);

            return deferred.promise;
        };
    }]);

Here you can see really simple service Greeter that has $q service injected. Injecting is made by passing an array as .service's second parameter. All strings (but last) are the services' names and the last parameter is a function that gets references or instances to those services as its parameter. It's clear, right? There's nothing wrong with declaring this anonymous as a named function outside the service declaration:

angular  
    .module('myModule')
    .service('Greeter', ['$q', Greeter]);

function Greeter($q) {  
    this.greetWithTimeout = function (name) {
        var deferred = $q.defer();
        setTimeout(function () {
            deferred.resolve("Hello, " + name);
        }, 5000);

        return deferred.promise;
    };
}

Looks really nice but what we are trying to do it do get rid of this ugly services array. If you take a close look at dependency injection guide you can see that we can use the $inject parameter. Let's try it out:

angular  
    .module('myModule')
    .service('Greeter', Greeter);

function Greeter($q) {  
    this.greetWithTimeout = function (name) {
        var deferred = $q.defer();
        setTimeout(function () {
            deferred.resolve("Hello, " + name);
        }, 5000);

        return deferred.promise;
    };
}

Greeter.$inject = ['$q'];  

And that's what I wanted to talk about. $inject is a function's static property that indicates what services are required to be injected to this function's constructor. Of course we still have an array but it looks much nicer and - what's the most important - we didn't use those services with angular service initialization (first three lines). As you may know, TypeScript has a support for static keyword so each class can contain the information about services, values, constants it needs. Pure syntax sugar.

A little come back to our project

To clear that out let's come back to the MyShop application that we have initialized in the previous post. Remember our ProductsService?

    export class ProductsService {
        private products: Product[] = [];

        getProducts(): Product[] {
            return this.products;
        }

        setProducts(products: Product[]):void {
            this.products = products;
        }
    }

...and remember I've said that setProducts will be a temporary method? Now I can tell you why. Honestly, setters are a bit shitty for that. We don't need them in this case. If we'd leave them each time we should call setProducts only to call getProducts just after or few lines later.

What do you think about fetching those products from remote server? How about we could use standard $http service? What if I told you angular definitons in tsd have also $http definitons? Sounds awesome. Let's see this in action.

Redefine expectations

By redefine I mean... saddly... to remove all tests that we have done so far for ProductsService and write our expectations again. Don't worry. So far we have written only two test cases for getting and settings the products. They were so simple that we could even not write them (if a property has a getter returning the contents of private property set by a setter, why not to make it public?).

While in production code we'll use $http service, in the production code we're going to use $httpBackend - the service available in angular.mock package. The usage is pretty simple: we can mock HTTP responses for some URL pattern, headers and body or even expect that given URL will be called. For the second case, when URL won't be invoked, the test will fail.

We are going to expect that request to /products.json will be invoked and then we are going to mock the response with 200 status code and JSON with our products. Let's write down the test for that:

// test/services/products_test.ts

/// <reference path="../../app/typings.d.ts" />

describe('ProductsService', () => {  
    var ProductsService: MyShop.ProductsService,
        $httpBackend: ng.IHttpBackendService;

    beforeEach(angular.mock.module('MyShop'));
    beforeEach(inject([
        'ProductsService',
        '$httpBackend',
        (p, $hb) => {
            ProductsService = p;
            $httpBackend = $hb;
        }
    ]));

    it('should fetch products from API', (done) => {
        var products = [
            {
                name: "jPhone 8",
                price:  999.99,
                description: "A super modern smartphone!"
            },
            {
                name: "GL 610",
                price: 1119.99,
                description: "UltraHD TV with super awesome remote controller made of glass!"
            },
            {
                name: "Venolo G510",
                price: 345.99,
                description: "New Venolo's product, super thin and super fast laptop!"
            }
        ];

        $httpBackend.expectGET('/products.json').respond(200, products);

        ProductsService
            .getProducts()
            .then((products: MyShop.Product[]) => {
                expect(products.length).toEqual(3);
                done();
            })
            .catch((err) => {
                done(err);
            });

        $httpBackend.flush();
    });
});

The getProducts method will be asynchronous so it will return a promise. With such behavior we need to pass done callback to notify our test runner that we have finished all expectations and test case is finished. We also chain our result promise with catch to catch possible exceptions. If such one occurs, we pass it into done parameter to fail our tests.

Since our Product model has no methods for checking properties of its instance, we can't check anything but number of elements returned by getProducts method. We can easily change that just by changing the scope of properties:

    export class Product {
        constructor (public name: string, public price: number, public description: string) {}
    }

Now when all of properties are public we can do another expectations.

expect(products[0].name).toEqual("jPhone");  
expect(products[0].price).toEqual(999.99);  
expect(products[0].description).toEqual("A super modern smartphone!");

// and so on...

done();  

Basically we expect that after retrieving a response from some "products API" we are going to have a collection of Product objects. Of course our tests will fail now because since the beginning of this article our ProductsService has been untouched. Let's move to probably the section you're probably the most interested in.

Injecting dependencies

We need to inject the $http service to fetch our products and $q for promises support. How to do this in our ProductsService? Since it's a POJO class and we know how it's done with pure JavaScript, we can inject it into the constructor. So, we have to implement a constructor with $http and $q services references. We also need to inject our dependencies with $inject static property. Just like we did in the introduction above with JavaScript. Let's those things in our service:

    export class ProductsService {
        static $inject = ['$http', '$q'];

        constructor (private $http: ng.IHttpService, private $q: ng.IQService) {}
        // ...
    }

But our tests still fail because we didn't request for our products. Moreover, we expect that promise returned with getProducts method will be resolved with array of Products while our API is going to respond with simple JSON. We need to iterate over the responded products array and create a Product for each of it.

getProducts(): ng.IPromise<Product[]>  
{
    var deferred = this.$q.defer<Product[]>();
    this.$http.get<any[]>('/products.json').then((response) => {
        var products = response.data;
        var result:Product[] = [];

        for (let i = 0, len = products.length; i < len; i++) {
            let product = products[i];
            result.push(new Product(product.name, product.price, product.description));
        }

        deferred.resolve(result);
    });

    return deferred.promise;
}

I think it's pretty simple. You can be a bit confused with < and > signs. Angular definitions for $http and $q include generic methods and classes. So with little improvements here we can help our IDE to be a bit more smarter. Passing proper type to $http.get we can specify what type will be our response.data of. Well, as our tests went green, we can go to the next step...

Clean up

The getProducts method is quite clean but, as I've just written, we can improve it a bit by passing proper type reference to the generic methods. It won't affect our tests result it may have an impact on the compilation if we make a mistake.
Another thing that should be placed in another place is Product creation. First: following the SRP it should be at least in another method to not to keep all responsibility in one place. Secondly: creating a model instance from JSON will be surely useful in the future, so we can avoid the duplication right now. Since we receive an array of products in the response we'll create the a factory for that. If we'll receive a single object (e.g. by querying for certain product with /products/1.json) we could do this in Product by a factory method. But we don't need it in the moment so let's skip this one.

At first create a test for ProductsFactory class (not existent yet) in the same file, but in the separate describe block. We are going to register it as a service to avoid duplication also in tests - in fact, we have already done it so we can copy all tests and inside ProductsService::getProducts test we'll only stub the factory's method. Since we've already written those test, I'm going to skip this snippet to not to make this article too long.
Create a class ProductsFactory, register it as a service in my_shop.ts in the similar way as ProductsService and implement the create method:

export class ProductsFactory {  
    create(rawProducts: IProduct[]): Product[] {
        var result: Product[] = [];

        for (let i = 0, len = rawProducts.length; i < len; i++) {
            let product = rawProducts[i];
            result.push(new Product(product.name, product.price, product.description));
        }

        return result;
    }
}

As you can see, I used IProduct type here. It's an interface with nothing but Products's interface. Thanks to TypeScript we can assume what properties will have raw object so the IDE is smarter from now. Here's the interface:

export interface IProduct {  
    name: string;
    price: number;
    description: string;
}

As the ProductsFactory tests goes green we can inject it into ProductsService and stub the create method because all we want to check is whether this method has been called and the result it returns will be resolved by $q instance. To complete the refactoring we need to adapt our tests and ProductsService itself. If you want to, do it by yourself. You can compare the results with the source code.


Not every element in our application is a service. We can also use Dependency Injection in directives, filters or other services. I'm going to describe all of them with details in my future articles.

Bonus

With the latest source code you can find Grunt configuration file for better TypeScript + Karma management. To run the whole testsuite, run grunt test or just grunt.

Source code

As in previous part, you can find the source code to the project with everything we've done so far. Feel free to visit project's page on GitHub or update to the latest tag - part-2 - by typing git checkout part-2.