Injecting a Service in Angular 2

This is a sequel to my blog “Getting Familiar with Angular 2 DI“. This time we’ll create a simple application that will use a ProductComponent to render product details and ProductService to supply the data about the product. In this blog I use Angular 2 Beta.0. The working application is deployed as a plunk and it’ll produce the page shown below.

ch4_running_di_sample

The ProductComponent can request the injection of the ProductService object by declaring the constructor argument with a type:

constructor(productService: ProductService)

The following image will give you a better idea how these component and service are related.

ch4_di_product2

The file index.html will host a root component of your app’s main page, which in turn will include ProductComponent that’s dependent on ProductService.

Note the import and export statements. The class definition of the ProductService starts with the export statement to enable other components to access its content. Accordingly, the ProductComponent includes the import statement providing the name of the class (ProductService) and the module being imported (located in the file product-service.ts).

The providers statement instructs Angular to provide an instance of the class ProductService when requested.

The ProductService may communicate with some server requesting details for the product selected on the Web page, but we’ll skip this part for now and will concentrate on how this service can be injected into ProductComponent.

Our app will consist of the following files:

* The file index.html that loads the code of the app.ts that renders root component, which uses ProductComponent
* The ProductComponent will be implemented in the file product.ts.
* The ProductService will be implemented in a file product-service.ts.

The file index.html performs three functions:

* Loads Angular and its dependencies
* Hosts a root component represented by the tag .
* Configures SystemJS and uses it to load the application component from app.ts.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Angular DI Sample</title>
  
  <script src="https://npmcdn.com/angular2@2.0.0-beta.0/bundles/angular2-polyfills.js"></script>
  <script src="https://npmcdn.com/typescript@1.7.5/lib/typescript.js"></script>
  <script src="https://npmcdn.com/systemjs@0.19.8/dist/system.src.js"></script>
  <script src="https://npmcdn.com/rxjs@5.0.0-beta.0/bundles/Rx.js"></script>
  <script src="https://npmcdn.com/angular2@2.0.0-beta.0/bundles/angular2.dev.js"></script>
  
  <script>
    System.config({
      transpiler: 'typescript',
      typescriptOptions: {emitDecoratorMetadata: true},
      packages: {app: {defaultExtension: 'ts'}}
    });
  </script>
</head>
<body>
  <disample-root>Loading...</disample-root>
  <script>System.import('app/app');</script>
</body>
</html>

The AppComponent is shown next. It hosts the ProductComponent, hence it needs to import and declare it in the property directives. Note that the name of the selector of the AppComponent is disample-root, and we used it in index.html above:

import {bootstrap} from 'angular2/platform/browser';
import {Component} from 'angular2/core';
import ProductComponent from './components/product';

@Component({selector: 'disample-root',
  template: `<h1> Basic Dependency Injection Sample</h1>
             <di-product-page></di-product-page>`,
  directives: [ProductComponent]
})
class AppComponent {}

bootstrap(AppComponent);

Based on the tag it’s easy to guess that there is a child component with the selector having this value. This selector is declared in ProductComponent, which will get its dependency (ProductService) injected via the constructor:

import {Component, bind} from 'angular2/core';
import {ProductService, Product} from "../services/product-service";

@Component({
  selector: 'di-product-page',
  template: `<div>
  <h1>Product Details</h1>
  <h2>Title: {{product.title}}</h2>
  <h2>Description: {{product.description}}</h2>
  <h2>Price: \${{product.price}}</h2>
</div>`,
  providers:[ProductService] // 1
})

export default class ProductComponent {
  product: Product;

  constructor( productService: ProductService) { // 2

    this.product = productService.getProduct();
  }
}

1. The providers property maps a token that we’ll use in the code to the name of the class that will represent this token. In this example the name of the token is the same as the name of the class: ProductService, so we use a short notation without invoking the function provide() with useClass.

2. Injection of the ProductService will be done here, and Angular will instantiate this object.

We separate the type ProductService from the actual implementation of this service being that a class ProductService, OtherProductService or something else. Replacing one implementation with another comes down to changing the providers line.

The constructor of ProductComponent invokes getProduct() on the service and places a reference to the returned Product object into the class variable product, which is used in the HTML template.

Using double curly braces the above markup allows us to bind the properties title, description, and price of the class Product. The file product-service.ts includes the declaration of two classes: Product and ProductService.

export class Product {  // 1
  constructor(
    public id: number,
    public title: string,
    public price: number,
    public description: string) {
  }
}

export class ProductService { 

  getProduct(): Product { // 2
    return new Product(0, "iPhone 7", 249.99, "The latest iPhone, 7-inch screen");
  }
}

1. The class Product represents a product (a.k.a. Value Object). This type will be used outside of this script so we export it.

2. For simplicity the method getProduct() always returns the same product with hard-coded values. In the real-world applications you’d need to get it from some external data source, e.g. by making an HTTP request to a remote server.

To see this example in action open this plunk and press the button Run.

The instance of ProductService was injected into ProductComponent, and the product component rendered product details provided by the server.

In the next section you’ll see a ProductService decorated with the @Injectable annotation, which is used for generating DI metadata in cases when the service itself uses DI as shown in the next section. It’s not needed here because this service doesn’t get any other service injected into it.

Injecting an Http Service

Pretty often a service would need to make an HTTP request to get the requested data. The ProductComponent depends on ProductService which will be injected using Angular DI mechanism. If the ProductService needs to make an HTTP request, it’ll have an Http object as its own dependency. The ProductService will need to import the Http object from Angular, and should have a constructor for injecting Http object. The following diagram shows that ProductComponent depends on ProductService, which has its own dependency: Http.

ch4_dependency_dependency

The following code snippet illustrates the Http object injection into ProductService and retrieval of products from the file products.json:

import {Http} from 'angular2/http';
import {ProductService} from "./product-service";

@Injectable
export class ProductService {
    constructor(private http:Http){
      let products = http.get('products.json');
    }
   // other app code goes here
   // will write a separate blog about using Http
}

The class constructor is the injection point, and we need to add the provider of object of the Http type. Angular offers a special object HTTP_PROVIDERS, which provides injectables for making HTTP requests and could be specified as an application-level dependency during the bootstrap. The application-level providers are available for all application components. The ProductComponent would need to explicitly specify the HTTP providers:

import {HTTP_PROVIDERS} from 'angular2/http';

class ProductComponent {

  constructor(private productService: ProductService) {}
  
  // The rest of the code goes here 
}
bootstrap(ProductComponent, [HTTP_PROVIDERS]);

Leaky Abstraction Detected. You need to specify the HTTP provider at the component level despite the fact that Http is used only inside the service (ProductComponent <-ProductService <- Http). This breaks encapsulation of the ProductService. In other words, the ProductService abstraction leaks. Imagine that ProductService is used in 10 different components, and for some reason we decided to update ProductService replacing HTTP with another way of getting product information. This would require code modification not only inside the ProductService, but also in those 10 components.

I'd like to keep the declaration of HTTP provider inside the ProductService, but currently it’s not possible. I raised this question at Angular’s repo, and you can join the discussion here

.

In one of the future blogs I'll show you how easy it is to replace one implementation of the service with another using Angular DI.

Stay tuned for more Angular 2 blogs. My other Angular-related blogs are here. Manning is publishing the drafts of our book “Angular 2 Development with TypeScript“. Starting on February 28, we’ll also teach an online class on Angular 2.

Advertisement

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 )

Facebook photo

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

Connecting to %s