Asynchronous Service

In a real app, we need to call the web service from the server, which is an inherently an asynchronous service call.

Till now, we have created the getProducts(), getProduct(), addProduct() and deleteProduct() product in ProductService.

This all method have a synchronous signature.

which implies for getProducts() that the ProductService can fetch products synchronously. The ProductsComponent consumes the getProducts() result as if products could be fetched synchronously. As shown below

this.products = productService.getProducts();

This will not work in a real app. We are getting away with it now because the service currently returns mock products. But soon the app will fetch products from a remote server, which is an inherently asynchronous service operation.

The ProductService must wait for the server to respond, getProducts() cannot return immediately with product data, and the browser will not block while the service waits.

ProductService.getProducts() must have an asynchronous signature of some kind.

It can take a callback. It could return a Promise or an Observable.

In this tutorial, we will update ProductService.getProducts() which will return an Observable in part because it will eventually use the Angular HttpClient.get() method to fetch the products and HttpClient.get() returns an Observable.

So let’s see the Observable first. 

Observable is one of the key classes in the RxJS library.

In the next chapter of HTTP Services, we will learn that Angular HttpClient methods which return RxJS Observables. So in this tutorial, we will simulate getting data from the server with the RxJS of() function

Open the ProductService file and import the Observable and of symbols from RxJS.

import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

replace getProducts() method  of ProductService with below code :

getProducts(): Observable<Product[]> {
    return of(this.products);
}

of(this.products) returns an Observable<Product[]> that emits a single value, the array of mock products.

In the HTTP tutorial, we will call HttpClient.get<Product[]>() which also returns an Observable<Product[]> that emits a single value, an array of products from the body of the HTTP response

Now you will get the error in the ProductsComponent as

Because now the ProductService.getProducts() method returns Observable<Product[]>.

We will resolve this error by subscribing the getProducts() method in ProductsComponent.

Before we update the getProducts() method in ProductsComponent, let’s move the service call code  from constructor to the ngOnInit()

as shown below,

ngOnInit() {
  this.products = this.productService.getProducts();
}

While we could call getProducts() in the constructor, that’s not the best practice.

Reserve the constructor for simple initialization such as wiring constructor parameters to properties. The constructor shouldn’t do anything. It certainly shouldn’t call a function that makes HTTP requests to a remote server as a real data service would.

Instead, call getProducts() inside the ngOnInit lifecycle hook and let Angular call ngOnInit at an appropriate time after constructing a ProductsComponent instance.

Find the getProducts() method and replace it with the following code.

ngOnInit() {
     this.productService.getProducts().subscribe(
          products => this.products = products
     );
}

Observable.subscribe() is the critical difference.

The previous version assigns an array of products to the component’s products property. The assignment occurs synchronously, as if the server could return products instantly or the browser could freeze the UI while it waited for the server’s response.

That won’t work when the ProductService is actually making requests of a remote server.

The new version waits for the Observable to emit the array of products— which could happen now or several minutes from now. Then subscribe passes the emitted array to the callback, which sets the component’s products property.

This asynchronous approach will work when the ProductService requests products from the server.

We will convert getProduct() method with asynchronous signature in ProductService. as shown below

getProduct(id: number): Observable {
    return of(this.products.find( p => p.id === id));
  }

Now subscribe this method in ProductDetailComponent as shown below,

this.productService.getProduct(id).subscribe(
        product => this.product = product
);

Here We will not convert addProduct() and deleteProduct() method of PorductService into asynchronous signature. We will change these methods in the HTTP chapter.

Now refresh the browser and check the output, you will get the same output as we have seen in the previous chapter, but this time we are doing asynchronous calling of getProducts() and getProduct().

import { MockData } from './../mock-data/mock-product-data';
import { Injectable } from '@angular/core';
import { Product } from '../models/product';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

@Injectable()
export class ProductService {

  products: Product[] = [];
  constructor() {
    this.products = MockData.Products;
  }

  getProducts(): Observable<Product[]> {
    return of(this.products);
  }

  addProduct(product: Product) {
    this.products.push(product);
  }

  getProduct(id: number): Observable<Product> {
    return of(this.products.find( p => p.id === id));
  }

  removeProduct(product: Product) {
    let index = this.products.indexOf(product);
    if (index !== -1) {
      this.products.splice(index, 1);
    }
  }
}
import { ProductService } from './../service/product.service';
import { Component, OnInit } from '@angular/core';
import { Product } from '../models/product';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {

  products: Product[] = [];

  constructor(public productService: ProductService) {
  }

  ngOnInit() {
     this.productService.getProducts().subscribe(
          products => this.products = products
     );
  }

  deleteProduct(product: Product) {
    this.productService.removeProduct(product);

    this.products = this.productService.getProducts();
  }

}
import { ProductService } from './../service/product.service';
import { Product } from './../models/product';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';

@Component({
  selector: 'app-product-detail',
  templateUrl: './product-detail.component.html',
  styleUrls: ['./product-detail.component.css']
})
export class ProductDetailComponent implements OnInit {

  product: Product;

  constructor(private activatedRoute: ActivatedRoute,
    private location: Location,
    private productService: ProductService) {
  }

  ngOnInit() {
    let id = +this.activatedRoute.snapshot.paramMap.get('id');
    this.productService.getProduct(id).subscribe(
        product => this.product = product
    );
  }
  goBack() {
    this.location.back();
  }

}

In this chapter, we have seen

  • The purpose of asynchronous service
  • use of Observable.
  • how to change the signature of getProducts() and getProduct() method of ProductService with asynchronous signature.
  • how to subscribe the asynchronous method in Component.