Dependency Injection Engine in Angular 2 - First look

Introductory Deep Dive!

Posted by Anas.R.Firdousi on November 10, 2016
18 mins read

Angular 2 is here and in a few months Angular 3 will be out too. Angular core team recently moved to semantic versioning which promises stable and well informed breaking changes and periodical enhancements! Out of the many powerful features that ships with Angular 2, we will cover the most fundamental one in this post, "Dependency Injection Engine" in Angular2 and is it really dependable? We will deep dive into the fundamentals of why Angular 2's DI is extremely powerful and robust.

What is Dependency Injection(DI)?

Before diving into Angular 2 semantics, doesn't it make more sense to do a crash course (or refresher) on dependency injection? So here is the crash course:

  • A component/class/controller(I will only use the word 'component' in this post) should not configure its dependencies statically. Dependencies should be configured from outside.
  • So a component should be as independent as possible. If you take out all hardcoded/tightly coupled dependencies and set it from outside, it makes the component highly reusable and configurable.
  • Dependency injection makes the code more testable. How? You can easily swap real dependencies with mock dependencies without effecting the actual component.
  • When your components are hierarchical, you can swap in and out different factories for the same dependency. Whaaaat? You can have same dependency resolving into different objects with the same tree. A child component may want to use a different form of the same dependency used by its parent component. Don't worry if you didn't completely get this one, this article will heavily emphasize on this concept.
Too much theory? Let's look at an example.

@Component({
    ..
})
export class UserLogin {

     constructor(){
        this.userInfoService = new UserInfoService();
    }

}

Suppose that this is your login screen component. It uses a UserInfoService to get user information, validate the credentials and allow user to navigate to the home page or deny in case of wrong credentials.

  • UserLogin component is dependent on UserInfoService to work. UserInfoService is a dependency.
  • The construction of UserInfoService new UserInfoService() is happening in the constructor. Constructor has taken the ownership of creating its own dependencies. Is it good? I don't think so.
  • 1  Is this code testable? No! How will you mock UserInfoService? Its all hardcoded in there.
    2  Tomm. we want to introduce 2 types of info services, one that validates user against LDAP (LDAPUserInfoService) and other which validates user against a database (DBUserInfoService). Do we have to change our code to accommodate this? If we have to, than this isn't quality code. Can we do better?


@Component({
    ..
})
export class UserLogin {

     constructor( userInfoService:UserInfoService ){
        this.userInfoService = userInfoService;
    }

}

This improved code looks testable! Rather then creating UserInfoService inside the component, we are injecting it into the component. This is a great win over hardcoded dependency creation. Now how can we swap different types of factories for this same UserInfoService?



interface UserInfoService {
    validateCredentials():boolean;
}

class LDAPUserInforService extends UserInfoService{
    validateCredentials():boolean{
    }
}

class DatabaseUserInforService extends UserInfoService{
    validateCredentials():boolean{
    }
}

@Component({
    ..
})
export class UserLogin {

     constructor( userInfoService:UserInfoService ){
        this.userInfoService = userInfoService;
    }

}

Congratulations on successfully completing the "Dependency Injection 101 Crash Course"!

What is dependency injection engine and injectors?

In the above example, we took out dependencies from the component class. We made our components independent and more testable. Our component doesn't create its own dependencies. But some one has to! right? A dependency injection (DI) engine is a machinery which takes care of creating dependencies and managing its life cycle. DI engine also manages the number of times an object will be constructed. Should the object (dependency) be constructed only once i.e should it be singleton and the same instance is provided to every component who wants to inject it or should there be a privilege to instantiate a type(dependency) more than once. All of this complex machinery is managed by something called DI Engine.. DI Engine has injectors which can help components get specific instance of different known type of objects.

Token to Object Mapping

To understand the below example, suppose that you already have instance of an Injector already present. The following example is just a pseudocode.


// Asking injector to setup objects of type User and Contact
let injector = injector.setup(User,Contact);


let user = injector.get('User'); // Passing a "string" token
let user = injector.get(User); // Passing a "type" token

    

What happens when we pass User as a string to the injector? The injector takes this string token and converts it into an Object(not really). We actually specify the "type of object" we want as a "string". Injector knows how to create that type of object and returns us a instance of that type. Will the injector return the same instance of that type of object or will it return a new instance every time? There is no definite answer to this and it depends on how the injector is configured. In Angular 2 for instance, the powerful injector of the DI engine is capable of doing both. It can keep dependencies as singleton and return back the same instance every time OR it can return a new instance of a type every time you ask for it.

Similarly, to some injectors, we can also pass a type(and not string) as in line no.7 above and get back instance of that type.

Hello Angular 1.x Developers!

If you are not a Angular 1 developer and you never did Angular before Angular 2, you can skip this para.

Lets recall how DI engine and injectors work in Angular 1.x . Something like :


angular
    .module('app.core')
    .controller('UserInfo', UserInfo);

UserInfo.$inject = ['$http', 'UserInfoService'];

function UserInfo($http, UserInfoService) {
}
    

You pass dependencies as a string to the $inject array. These string tokens are then mapped to controller function parameters so they have to be in the same order as shown in the above example. The DI engine was pretty naive in Angular 1 and just scratched the surface. There were few issues:

  • Services in Angular 1 were essentially singletons. If you wanted to have multiple instanced of a service, you had to create your own object factory which could return more than one instance or probably a new instance every time , which ever way you configure it. This was a lot of work which you had to do as a developer, the work which should ideally be done by DI engine seamlessly. Angular 2 DI solved this.
  • Namespace issues are also part of any naive DI system like Angular 1's. You may end up writing a service with the exact same name as any other third-party library you are using. The only thing you could do was to rename your service with a different name. This didn't seem write. More than 1 service can have the exact same but may want to function differently. This is also solved by Angular 2's DI.
  • String tokens wasn't a problem but was the only choice to ask for a dependency in Angular 1. You may want to specify a type (and not string) to get instance of that type. Angular 2's DI allows you to do that.

Angular 2's Dependency Injection Engine

Every Angular 2 application needs to have at least 1 ngModule. ngModule is a container of multiple components, services, pipes e.t.c. Starting from root component, every component in the application tree has its own "Injector"

What is a Injector?

An object that has the capability to create instances of dependencies. You pass either a string token or specify a type to get that an instance of that type of object.

Any Angular 2 application is a tree of components. Since every component gets its own injector, think of it as a tree of injectors. Let's look at an example below and see how we will generally split parts of our application into components:

Ah ! A todo app !

Now how do you break this application into component tree?

Todo App - Component Tree

This basically creates a injector tree with each component having their own injector. Todo App - Injector Tree

Let's assume that our application has only 1 NgModule which looks like


    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { UserService } from './shared/user';

    import { AppComponent }  from './app.component';

    @NgModule({
        imports:      [ BrowserModule],
        declarations: [ AppComponent ],
        bootstrap:    [ AppComponent ],
        providers: [ UserService ],
    })
    export class AppModule { }
    

If we inject a service at the top and a leaf component asks for that dependency, it will walk all the way up to root injector to find the dependency. If you have observed, we injected a "UserService" into our root (ngModule) using the "providers" property in @NgModule() decorator. Look at line 12. We will discuss "providers" in detail in a bit. Now assume that one of the leaf component asks for "UserService". What happens?

This is how our code looks like on the leaf component level,


    import { ... } from '...'; // excluding import details for brevity

    @Component({
        selector: 'app',
        template: '{{task.name}}'
    })
    class TodoItem {
        @Input() task

        constructor( private userService:UserService ){
            //wants to do something with UserService
        }
    }
    

Notice that we did not use the "providers" property on our @Component(). Using a "provider" property here will give a new instance of 'UserService" to this component which we don't want for now.

Invertly, you can think of it like this: If you inject a dependency into the root, the same instance of that dependency is available to all children in the tree.

At this point, our "UserService" object is singleton. What if I want a different instance of "UserService" for all the list components in my component tree. If I can configure my main list component to get a new instance of "UserService" from its own injector , that it can supply that new instance to all of its child components. You can do this in Angular 2 and this is how it will look:

Code for our "list" component will look like:


    import { ... } from '...'; // excluding import details for brevity
    import { UserService } from './shared/user';

    @Component({
        selector: 'app',
        template: '
            <ul>
                <li *ngFor="let item of list">
                    <todo-item [task]="item"></todo-item>
                </li>
            </ul>
        ',
        providers: [UserService],
        declarations: [TodoItem]
    })
    class TodoList {

        constructor( private userService:UserService ){
            //wants to do something with UserService
        }
    }
    

In the above example, notice line 14 where we are using the "providers" property again. Essentially, the TodoList component is asking for a fresh instance of type "UserService" for itself and all its children. In other words, "TodoList" has shadowed the "UserService" dependency and has given new instance to itself and all child components.

However in real world applications, there can be situation in which one of the leaf components may want to bypass the dependency provided by parent and point to a ancestor dependency instance. This is also possible with Angualr 2's powerful DI engine and this is how it will look like:

Code?

To achieve this, you have to do 2 things:

  • Using "providers" property , create an "alias" for the actual dependency in root node other than the actual dependency.
  • To bypass the shadowed instance of dependency, use the "alias" which has not been shadowed by the immediate parent.

    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { UserService } from './shared/user';

    import { AppComponent }  from './app.component';

    @NgModule({
        imports:      [ BrowserModule],
        declarations: [ AppComponent ],
        bootstrap:    [ AppComponent ],
        providers: [
            UserService,
            { provide: RootUserService, useExisting: UserService }
        ],
    })
    export class AppModule { }
    

By using the a more descriptive version of "providers" at line 14, we created an alias for the "UserService" instance injected in root. Look closely at the use of useExisting .

How do you read line 13? Inject "UserSerive" into AppModule and make it visible for all its children until some child shadows it with a new instance (as we did in last example).
How do you read line 14? If a descendant ask for the original instance of "UserService" which is injected in this root module, they can ask for it without the fear of shadowing using an alias "RootUserService". The "useExisting" makes sense because we are using a dependency which is already injected in this module i.e the "UserService"

Line 13 above can also be written in the expressive form as well:

    ...

    @NgModule({
        ...
        providers: [
            { provide: UserService, useClass: UserService }
            { provide: RootUserService, useExisting: UserService }
        ],
    })
    export class AppModule { }
    


Notice the use of "userClass" on line 7. We are passing a type token (not a string token) in the "provide" property and "useClass" property is used to provide the actual type whose instance will be provided. This is what "providers" do.

When your "token" and "type" is same, you should prefer to use the short syntax.

What actually is a Provider?

We learned "injectors" create dependency objects for us. "Provider" is an object that gives instructions to the injector on how to create those dependencies. Providers provide configuration facilities to the injector. Provider accepts a token(string or type token) for injectors and then tells the injector which type of object to create for that token.

The expressive form of providers are powerful. We just created an alias within the root component so that we can bypass the hierarchical nature of injectors. This may or may not be the good solution for your use-case. I don't want to get into the details of merits and demerits of doing this on parent(root). Another option, perhaps cleaner, is to use "Opaque Tokens" and create an external reference that can be used by component irrespective of the hierarchy.
Here is a quick example:

    
     // app.tokens.ts

    import { OpaqueToken } from '@angular/core';
    export const ROOT_USERSERVICE = new OpaqueToken('UserService');
    

Now in the your main NgModule

    
     // app.module.ts
    import { ... } from '...'; // excluding import details for brevity
    import { ROOT_DATASERVICE } from './app.tokens';

    @NgModule({
    imports:      [ BrowserModule],
    declarations: [ AppComponent, TodoList, ListItem ],
    bootstrap:    [ AppComponent ],
    providers: [
        UserService,
        { provide: ROOT_USER_SERVICE, useExisting: UserService }
    ],
    })
    export class AppModule { }

    

Now if the ListIten component wants to bypass the injected "UserService" from its immediate parent "TodoList" and get the dependency from root component, it can do it using the same Opaque token. Something like:

    
    import { ROOT_USER_SERVICE } from './app.tokens';

    @Component({
        selector: 'task-item',
        template: '{{task.name}}'
    })
    class TodoItem {
        @Input() task

        constructor( @Inject(ROOT_USER_SERVICE) private userService ){

        }
    }
    

The @Inject() in the constructor parameter may look like a burden in the start but this is how you will be injecting dependencies using Opaque Token. Opaque Token is a powerful concept used internally by the framework and out of this post's scope.

Other provider configuration that are useful:

  • useValues
    
        import {Component, Inject} from '@angular/core'
    
        @Component({
            selector: 'app',
            template: 'Main App: URL : {{URL}}',
            providers:[
                { provide: 'DATA_URL', useValue: 'http://dummyendpoint.data.com' }
            ]
        })
        class App {
            constructor(@Inject('DATA_URL') private url){
                this.URL = url;
            }
        }
    
    
  • useClass in combination with useExisting

    We used this technique to create alias.

    
        { provide: UserService, useClass: UserService }
        { provide: LocalUserService, useExisting: UserService }
    
    

    We can also swap our dependency with Mock dependencies in unit tests.

            
                { provide: UserService, useClass: MockUserService }
            
            

  • useFactories

    If you would like to write some complex logic or want to decide between more than choices on the fly, you can use useFactory

    
        {
            provide:UserService,
            userFactory:()=>{
                if(DEV_ENVIRONMENT){
                    return new LDAPUserService()
                }else{
                    return new LocalUserService()
                }
            }
        }
    
    

    Factories can be complex and have their own dependencies. To provide inner dependencies to factories , you will have to use the "deps" property in combination with "useFactory"

    
        {
            provide: UserService,
            useFactory: (credentialService,endPointService)=>{
    
            },
            deps:[CredentialService,EndPointService]
        }
    
    
  • Optional Properties with @Optional Decorators

    If you want to provide fallback in situation where you expect a depedency but still want to run the system in case they are not provided:

    
        { provide: UserService, useClass: UserService }
        { provide: LocalUserService, useExisting: UserService }
    
    

    We can also swap our dependency with Mock dependencies in unit tests.

            
                class Car {
                  constructor(@Optional(d3) de) {
                    if (!d3) {
                        // dependency fallback
                    }
                  }
                }
            
            

We did not cover everything related to dependency injection in Angular 2 but yet you can feel the power of it. This is good enough to grasp and play with for one post. In my next post I will discuss dependency injection in Angular 2 with more advance topics like what goes behind the scenes with Injectors in Angular 2, how to prevent name collisions, how to use multi providers and what makes them useful, host component visibility in Angular 2 and much more. Stay tuned!

Feel free to leave a comment, question, suggestion and corrections. Until next time, Happy learning!