@Injectables + Angular 2.0 + @Inject

@Injectable() is the decorator in Angular 2.0

Typically in Angular 2.0 services are simple classes and if these services require dependencies to be injected then you need this decorator.

DI (Dependency Injection) in Angular 2.0 is hierarchical in nature, for dependency injection to be able to create instances for you, you need to register providers for these classes (or other values) somewhere. and you can configure providers for DI at different levels

  • For the whole application when bootstrapping it. In this cases, all sub injectors (the component ones) will see this provider and share the instance associated with. When interacting, it will be the same instance
  • For a specific component and its sub components. Same as before but for à specific component. Other components won’t see this provider. If you redefine something defined above (when bootstrapping for example), this provider will be used instead. So you can override things. If a provider is registered in one of the child components a new (different) instance is provided for descendants of this component.
  • If a component requests an instance (by a constructor parameter), DI looks “upwards” the component tree (starting from leaf towards the root) and takes the first provider it finds. If an instance for this provider was already created previously, this instance is used, otherwise a new instance is created.

Angular 2.0 docs recommend adding @Injectable() to all services classes , even those that don’t have dependencies and, therefore, do not technically require it. Here’s why:

  • Future proofing: No need to remember @Injectable() when we add a dependency later.
  • Consistency: All services follow the same rules, and we don’t have to wonder why a decorator is missing.

Here is sample service (CategoryService) for my mobile app

import { Injectable } from '@angular/core';
import { Http, Response} from '@angular/http';

import {Category} from './category';
import {Observable} from 'rxjs/Observable';

@Injectable()
export class CategoryService {
    constructor(private http: Http){
        
    }
    
    private categoryServiceUrl = "http://app.cloudapp.net/api/Category";
    
    getCategories (): Observable<Category[]>{
        return this.http.get(this.categoryServiceUrl)
                        .map(this.extractCategoryData)
                        .catch(this.handleError);
                        
    } 
    
    private extractCategoryData(res: Response){
        let body = res.json();
        return body || { };
    }
    
    private handleError (error: any) {
        let errMsg = (error.message) ? error.message : 
        error.status ? `${error.status} - ${error.statusText}` : 'Server error';
        console.error(errMsg); 
        return Observable.throw(errMsg);
  }
}

 

Here is how you register with provider this is at component level

import { Component, OnInit } from '@angular/core';
import { Category } from './category';
import { CategoryService } from './categoryService';
// Add the RxJS Observable operators we need in this app.
import './rxjs-operators';
@Component({
  moduleId: module.id,
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css'],
  providers:[CategoryService]
})
export class AppComponent implements OnInit{
  title = 'app works!';
  firstName= "Aamol Gote";
  errorMessage: string;
  categories: Category[];
  
  constructor(private categoryService: CategoryService){
    
  }
  
  ngOnInit() {
    this.getCategories();
  }
  
  getCategories(){
    this.categoryService.getCategories()
                        .subscribe(
                          categories => this.categories = categories,
                          error => this.errorMessage = <any>error
                        );
  }
}

 

@Inject – This is manual way of letting angular know that this parameter must be injected. typically DI is implicit in nature for Angular 2.0 but for certain edge cases if there is need to specify dependency explicitly with its type then @Inject is used. One very good candidate for @Inject is for injecting non class dependencies. Suppose if you have an application configuration which includes API end point, then these configurations need not necessarily be instances of the classes these can be just object literals, for e.g. as shown below

export interface ApplicationConfiguration{
    apiEndPoint : string,
    timeOut: number
}

export const INTERVIEW_APP_CONFIG: ApplicationConfiguration = {
  apiEndPoint: 'http://app.cloudapp.net/api/Category',
  timeOut: 20
};

 

Now we need to inject INTERVIEW_APP_CONFIG in CategoryService, way we do it first need to register the Provider for INTERVIEW_APP_CONFIG,

[{ provide: InterviewAppConfig, useValue: INTERVIEW_APP_CONFIG  })]

constructor(private config:InterviewAppConfig){ }

 

But the above code snippet fails as INTERVIEW_APP_CONFIG constant has an interface, InterviewAppConfig. We cannot use a TypeScript interface as a token, to solve this we have to use Opaque tokens, here is the updated category service class with config value injected through @Inject()

import { Injectable, Inject } from '@angular/core';
import { Http, Response} from '@angular/http';
import { OpaqueToken } from '@angular/core';

import {Category} from './category';
import {Observable} from 'rxjs/Observable';

export let INTERVIEW_APP_CONFIG = new OpaqueToken('app.config');
export interface ApplicationConfiguration{
    apiEndPoint : string,
    timeOut: number
}

export const INTERVIEW_APP_DI_CONFIG: ApplicationConfiguration = {
  apiEndPoint: 'http://app.cloudapp.net/api/Category',
  timeOut: 20
};


@Injectable()
export class CategoryService {
    categoryServiceUrl :string;
    constructor(private http: Http, @Inject(INTERVIEW_APP_CONFIG) 
        config: ApplicationConfiguration){
        this.categoryServiceUrl = config.apiEndPoint;
    }
    
    
    
    getCategories (): Observable<Category[]>{
        console.log("Get Categories");
        return this.http.get(this.categoryServiceUrl)
                        .map(this.extractCategoryData)
                        .catch(this.handleError);
                        
    } 
    
    private extractCategoryData(res: Response){
        let body = res.json();
        return body || { };
    }
    
    private handleError (error: any) {
        let errMsg = (error.message) ? error.message : 
        error.status ? `${error.status} - ${error.statusText}` : 'Server error';
        console.error(errMsg); // log to console instead
        return Observable.throw(errMsg);
  }
}

 

We need register the provider in the component like shown below

@Component({
  moduleId: module.id,
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css'],
  providers:[CategoryService, { provide: INTERVIEW_APP_CONFIG, 
                                useValue: INTERVIEW_APP_DI_CONFIG }]
})
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s