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
.
- Note
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
.
- Note
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()
andgetProduct()
method ofProductService
with asynchronous signature. - how to subscribe the asynchronous method in Component.