Angular + Observable Example
February 28, 2018
This page will walk through how to use
Observable
in our Angular application. Observable
belongs to RxJS library. To perform asynchronous programming in Angular application we can use either Observable
or Promise
. When we send and receive data over HTTP, we need to deal it asynchronously because fetching data over HTTP may take time. Observable
is subscribed by using async
pipe or by using subscribe
method.
Observable
is a class of RxJS library. RxJS is ReactiveX library for JavaScript that performs reactive programming. Observable
represents any set of values over any amount of time. Observable
plays most basic role in reactive programming with RxJS. Some methods of Observable
class are subscribe
, map
, mergeMap
, switchMap
, exhaustMap
, debounceTime
, of
, retry
, catch
, throw
etc.
To use RxJS library in Angular programming we need to ensure that we have installed RxJS. In case we are using Angular CLI, it installs RxJS by default. We can ensure it by checking
package.json
. If RxJS is not there in package.json
, we can install it as following.
npm install rxjs --save
Observable
in our Angular application, we need to import it as following.
import { Observable } from 'rxjs/Observable';
Observable
in our Angular application step by step with complete example.
Contents
- 1. Technologies Used
- 2. Angular HttpClient and Observable
- 3. Observable + Async Pipe + NgFor
- 4. Observable + Async Pipe + NgIf
- 5. Observable subscribe
- 6. Observable map
- 7. Observable mergeMap
- 8. Observable switchMap
- 9. Observable of
- 10. Observable Error Handling
- 11. Angular In-Memory Web API to Test Application
- 12. Complete Example
- 13. Run Application
- 14. References
- 15. Download Source Code
1. Technologies Used
Find the technologies being used in our example.1. Angular 5.2.0
2. Angular CLI 1.7.1
3. TypeScript 2.5.3
4. Node.js 6.11.0
5. NPM 3.10.10
6. In-Memory Web API 0.5.3
2. Angular HttpClient and Observable
AngularHttpClient
performs HTTP requests for the given URL. HttpClient
works with Observable
. Find some of its methods.
get(url: string, options: {...}): Observable<any> post(url: string, body: any | null, options: {...}): Observable<any> put(url: string, body: any | null, options: {...}): Observable<any> delete(url: string, options: {...}): Observable<any>
HttpClient
returns instance of Observable
. When we subscribe to the Observable
instance, then actual HTTP operation is performed. The Observable
instances can also be executed using Async Pipe. Find the sample code to use HttpClient
to perform HTTP GET operations.
getBooksFromStore(): Observable<Book[]> { return this.http.get<Book[]>(this.bookUrl); }
HttpClient.get
returns Observable<any>
but we can generalize it by our required data type, for example we are returning here Observable<Book[]>
.
3. Observable + Async Pipe + NgFor
Angularasync
pipe subscribes to Observable
and returns its last emitted value. Here we will provide code snippets to use Observable
using async
pipe with ngFor
.
Step-1: We will create a method to fetch data over HTTP using Angular
HttpClient
in our service, suppose in BookService
, as following.
getBooksFromStore(): Observable<Book[]> { return this.http.get<Book[]>(this.bookUrl); }
Observable<Book[]>
.
Step-2: In our component we will create a property.
allBooks$: Observable<Book[]>
Observable
variable.
Now we will call the service method and assign value to the allBooks$
property in our component.
getBooks() { this.allBooks$ = this.bookService.getBooksFromStore(); }
ngOnInit
.
ngOnInit() { this.getBooks(); }
Observable
variable i.e. allBooks$
using async
pipe. We need to keep in mind that HTTP hit will take place only when we subscribe our Observable
variable. In our HTML template we will subscribe our Observable
variable i.e. allBooks$
using async
pipe with ngFor
.
<ul> <li *ngFor="let book of allBooks$ | async" > Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </li> </ul>
4. Observable + Async Pipe + NgIf
Here we will provide how to useObservable
using async
pipe with ngIf
. We will fetch data over HTTP and will display it. We will show loading image while data is being fetched over HTTP.
Step-1: We have created a method in service, suppose in
BookService
, to fetch data over HTTP for the demo.
getFavBookFromStore(id: number): Observable<Book> { return this.http.get<Book>(this.bookUrl + "/" + id); }
Observable<Book>
.
Step-2: In component we have created a property of
Observable
type.
favBook$: Observable<Book>
favBook$
as following.
getFavBook() { this.favBook$ = this.bookService.getFavBookFromStore(101); }
ngOnInit
for demo.
ngOnInit() { this.getFavBook(); }
Observable
variable favBook$
using async
pipe.
<div *ngIf="favBook$ | async as book; else loading"> Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </div> <ng-template #loading> <img src="assets/images/loading.gif"> </ng-template>
5. Observable subscribe
subscribe
is a method of Observable
class. subscribe
is used to invoke Observable
to execute and then it emits the result. If we have an Observable
variable that fetches data over an HTTP then actual hit to server takes place only when we subscribe to Observable
using subscribe
method or async
pipe. Here we will discuss subscribe
method to subscribe to Observable
.
Step-1: We have created a method in service, suppose in
BookService
, to fetch data over HTTP.
getBooksFromStore(): Observable<Book[]> { return this.http.get<Book[]>(this.bookUrl); }
Observable<Book[]>
Step-2: We will create a component property as following.
softBooks: Book[];
Observable
result of getBooksFromStore()
as following.
getsoftBooks() { this.bookService.getBooksFromStore().subscribe(books => this.softBooks = books); }
ngOnInit
.
ngOnInit() { this.getsoftBooks(); }
softBooks
in HTML template using ngFor
.
<ul> <li *ngFor="let book of softBooks" > Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </li> </ul>
6. Observable map
map
is a method of Observable
class. map
applies a given function to the value of its source Observable
and then returns result as Observable
. map
is imported as following.
import 'rxjs/add/operator/map';
map
in our Angular application.
favBookName$: Observable<string> getBookName() { this.favBookName$ = this.bookService.getFavBookFromStore(101).map(book=> book.name); }
getFavBookFromStore
service method is returning Observable<Book>
. Using map
we have converted it into Observable<string>
. The component property favBookName$
will now only emit book name. getFavBookFromStore
is defined in BookService
as follows.
getFavBookFromStore(id: number): Observable<Book> { return this.http.get<Book>(this.bookUrl + "/" + id); }
favBookName$
can be displayed in HTML template as following.
<div *ngIf="favBookName$ | async as bookName"> Name: {{bookName}} </div>
map
with subscribe
.
favBookName: string; getBookName() { this.bookService.getFavBookFromStore(101).map(book=> book.name).subscribe(name=> { this.favBookName = name; console.log(name); }); }
7. Observable mergeMap
mergeMap
is a method of Observable
class. mergeMap
emits Observable
based on the given function. If inner functions returns Observable
then final output will be the result of inner Observable
emitted data. If it gets multiple request before completing one, then mergeMap
maps values by combining those multiple inner Observable
into one by merging their emissions. mergeMap
is imported as following.
import 'rxjs/add/operator/mergeMap';
mergeMap
in our Angular application.
myAllfavBooks$: Observable<Book[]> getAllFavBooks() { this.myAllfavBooks$ = this.bookService.getFavBookFromStore(101) .mergeMap(book => this.bookService.getBooksByCategoryFromStore(book.category)); }
getFavBookFromStore
method then by using this book's category we are fetching all books of same category using getBooksByCategoryFromStore
method. Our service methods are defined in BookService
as follows.
getFavBookFromStore(id: number): Observable<Book> { return this.http.get<Book>(this.bookUrl + "/" + id); } getBooksByCategoryFromStore(category: string): Observable<Book[]> { return this.http.get<Book[]>(this.bookUrl + "?category=" + category); }
myAllfavBooks$
can be displayed in HTML template as following.
<ul> <li *ngFor="let book of myAllfavBooks$ | async" > Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </li> </ul>
subscribe
with mergeMap
we can do it as given below.
allFavBooks: Book[]; getAllFavBooks() { this.bookService.getFavBookFromStore(101).mergeMap(book => { let category = book.category; return this.bookService.getBooksByCategoryFromStore(category); }).subscribe(books => { this.allFavBooks = books; }); }
8. Observable switchMap
switchMap
is a method of Observable
class. switchMap
returns an Observable
that emits data by applying the given function. If we have provided an inner Observable
to our switchMap
function then the final output will be emitted data by inner Observable
. If switchMap
receives multiple requests to execute inner Observable
before completing the existing inner Observable
, then it stops the existing execution and starts executing only the latest inner Observable
execution request and keeps on doing so. The use case for switchMap
could be search functionality where user keeps on changing search keyword before getting result and requires only result for latest search keyword. switchMap
is imported in Angular application as following.
import 'rxjs/add/operator/switchMap';
switchMap
.
similarBooks$: Observable<Book[]> searchSimilarBooks(id: number) { this.similarBooks$ = this.bookService.getFavBookFromStore(id) .switchMap(book => { let category = book.category; return this.bookService.getBooksByCategoryFromStore(category); }) .catch(err => of([])); }
getFavBookFromStore
is returning Observable<Book>
and on the basis of book category we are fetching all books of same category by using getBooksByCategoryFromStore
that returns Observable<Book[]>
. Our final output is the output of inner Observable
and hence the final output will be of Observable<Book[]>
type. In our BookService
these methods are defined as following.
getFavBookFromStore(id: number): Observable<Book> { return this.http.get<Book>(this.bookUrl + "/" + id); } getBooksByCategoryFromStore(category: string): Observable<Book[]> { return this.http.get<Book[]>(this.bookUrl + "?category=" + category); }
switchMap
with subscribe
.
similarFavBooks: Book[]; searchSimilarBooks(id: number) { this.bookService.getFavBookFromStore(id) .switchMap(book => { let category = book.category; return this.bookService.getBooksByCategoryFromStore(category); }) .catch(err => of([])) .subscribe(books => { this.similarFavBooks = books; console.log(books); }); }
9. Observable of
of
is a static method of Observable
. of
is used to create a simple Observable
using data passed as argument. of
is imported as following.
import { of } from 'rxjs/observable/of';
const
value as following.
const bookDetails: Book[] = [ { id: 101, name: 'Angular by Krishna', category: 'Angular' }, { id: 102, name: 'Core Java by Vishnu', category: 'Java' }, { id: 103, name: 'NgRx by Rama', category: 'Angular' } ];
bookDetails
into Observable
by calling of(bookDetails)
. We can create a method to get this result as following.
getBooksFromStore(): Observable<Book[]> { return of(bookDetails); }
Observable
as following
import { Observable } from 'rxjs/Rx';
Observable
and of
both as following
import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of';
of
as Observable.of
and we can write our method as below.
getBooksFromStore(): Observable<Book[]> { return Observable.of(bookDetails); }
10. Observable Error Handling
To handle error inObservable
, it provides methods such as throw
, catch
, retry
. The method throw
can throw custom error. catch
method caches the error thrown by Observable
and then it can either return new Observable
value or can throw custom error using throw
. The method retry
just retries executing Observable
for the given number of times. To handle error in subscribe
method, the second argument of subscribe
caches the error. Here we will describe all these error handling in detail.
10.1 Observable throw
throw
creates an Observable
that immediately emits error. throw
is a static method of Observable
. It is used to conditionally throw error from map
, mergeMap
, switchMap
etc. We can import throw
as following.
import 'rxjs/add/Observable/throw';
throw
in our Angular application.
return this.bookService.getFavBookFromStore(101) .map(book=> { if(book.name.length < 15) { return book.name; } else { throw('Length less than 15'); } });
getFavBookFromStore
returns Observable<Book>
.
If we want to call
throw
using Observable
, we need to import Observable
as given below.
import { Observable } from 'rxjs/Rx';
throw
as Observable.throw
.
10.2 Observable catch
catch
is a method of Observable
class. catch
method catches errors in the Observable
to handle it by returning a new Observable
or throwing an error. We can import catch
in our Angular application as following.
import 'rxjs/add/operator/catch';
catch
.
return this.bookService.getFavBookFromStore(101) .map(book=> { if(book.name.length < 15) { return book.name; } else { throw('Length less than 15'); } }) .catch(error => { console.log(error); //return of("Default Name"); throw(error.message || error); });
catch
block, we can return any new value or can throw error.
a. Returning a value: If from
catch
block we want to return a value, we can do it by returning any Observable
instance.
.catch(error => { console.log(error); return of("Default Name"); });
catch
block we want to throw error, we can throw error as following.
.catch(error => { console.log(error); throw(error.message || error); });
10.3 Observable subscribe Error Handling
In thesubscribe
method of Observable
, second argument is to catch error. We can catch error in subscribe
method while subscribing to Observable
as following.
this.bookService.getFavBookFromStore(101) .map(book=> { if(book.name.length < 15) { return book.name; } else { throw('Length less than 15'); } }) .catch(error => { console.log(error); //return of("Default Name"); throw(error.message || error); }) .subscribe(name=> { this.bookName = name; console.log(name); }, err => { console.log(err); } );
Using HttpErrorResponse with subscribe:
When we have an
Observable
instance obtained from HttpClient
methods, there could be scenario as following.
a. If backend returns unsuccessful response codes such as 404, 500 etc then
HttpClient
will throw error with backend response code.
b. If something goes wrong client-side, for example, an exception is thrown by RxJS operators or if network error prevents the request from completing successfully then actual
Error
object is thrown by HttpClient
.
We can check if error is of
Error
type or not by using HttpErrorResponse
and accordingly log the error messages.
HttpErrorResponse
can be imported from @angular/common/http
library as following.
import { HttpErrorResponse } from '@angular/common/http';
this.bookService.getBooksFromStore().subscribe(books => { this.softBooks = books }, (err: HttpErrorResponse) => { if (err.error instanceof Error) { //A client-side or network error occurred. console.log('An error occurred:', err.error.message); } else { //Backend returns unsuccessful response codes such as 404, 500 etc. console.log('Backend returned status code: ', err.status); console.log('Response body:', err.error); } } );
10.4 Observable retry
retry
is the method of Observable
class. Some errors can be handled by just retrying request such as if errors are transient and unlikely to repeat. For example if we have slow network error and our request could not become successful then there are the chances to make request successful if request is retried.
We can retry request automatically by using
retry
operator. It accepts number argument. Suppose we pass argument as retry(3)
then the request will be retried for 3 times. retry
is imported from RxJS as following.
import 'rxjs/add/operator/retry';
retry
.
this.bookService.getBooksFromStore().retry(3).subscribe(books => { this.softBooks = books }, (err: HttpErrorResponse) => { if (err.error instanceof Error) { //A client-side or network error occurred. console.log('An error occurred:', err.error.message); } else { //Backend returns unsuccessful response codes such as 404, 500 etc. console.log('Backend returned status code: ', err.status); console.log('Response body:', err.error); } } );
11. Angular In-Memory Web API to Test Application
To test our application we need Web Service URL. Angular provides In-Memory Web API that will provide Web Service URL. We can configure URLs with dummy data using In-Memory Web API. Find the steps to use Angular In-Memory Web API.1. To install Angular In-Memory Web API, run following command.
npm i angular-in-memory-web-api@0.5.3 --save
InMemoryDbService
. Define createDb()
method with some dummy data.
test-data.ts
import { InMemoryDbService } from 'angular-in-memory-web-api'; export class TestData implements InMemoryDbService { createDb() { let bookDetails = [ { id: 101, name: 'Angular by Krishna', category: 'Angular' }, { id: 102, name: 'Core Java by Vishnu', category: 'Java' }, { id: 103, name: 'NgRx by Rama', category: 'Angular' } ]; return { books: bookDetails }; } }
/api/books
InMemoryWebApiModule
in application module and configure TestData
class as following.
import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { TestData } from './test-data'; @NgModule({ imports: [ ------ InMemoryWebApiModule.forRoot(TestData) ], ------ }) export class AppModule { }
12. Complete Example
Find the project structure.my-app | |--src | | | |--app | | | | | |--book.ts | | |--book.service.ts | | |--book.component.ts | | |--book.component.html | | | | | |--test-data.ts | | | | | |--app.component.ts | | |--app.module.ts | | | |--main.ts | |--index.html | |--styles.css | | |--assets | | | | | |--images | | | | | | | |--loading.gif | | | | |--node_modules |--package.json
book.ts
export interface Book { id: number; name: string; category: string; }
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { Book } from './book'; @Injectable() export class BookService { bookUrl = "/api/books"; constructor(private http: HttpClient) { } getBooksFromStore(): Observable<Book[]> { return this.http.get<Book[]>(this.bookUrl); } getFavBookFromStore(id: number): Observable<Book> { return this.http.get<Book>(this.bookUrl + "/" + id); } getBooksByCategoryFromStore(category: string): Observable<Book[]> { return this.http.get<Book[]>(this.bookUrl + "?category=" + category); } }
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/Observable/throw'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/retry'; import { HttpErrorResponse } from '@angular/common/http'; import { BookService } from './book.service'; import { Book } from './book'; @Component({ selector: 'app-book', templateUrl: './book.component.html' }) export class BookComponent implements OnInit { allBooks$: Observable<Book[]> favBook$: Observable<Book> myAllfavBooks$: Observable<Book[]> favBookName$: Observable<string> similarBooks$: Observable<Book[]> softBooks: Book[]; allFavBooks: Book[]; bookName: string | {}; similarFavBooks: Book[]; constructor(private bookService: BookService) { } ngOnInit() { this.getBooks(); this.getFavBook(); this.getsoftBooks(); this.getAllFavBooks(); this.getBookName(); } getBooks() { this.allBooks$ = this.bookService.getBooksFromStore(); } getFavBook() { this.favBook$ = this.bookService.getFavBookFromStore(101); } getsoftBooks() { this.bookService.getBooksFromStore().retry(3).subscribe(books => { this.softBooks = books }, (err: HttpErrorResponse) => { if (err.error instanceof Error) { //A client-side or network error occurred. console.log('An error occurred:', err.error.message); } else { //Backend returns unsuccessful response codes such as 404, 500 etc. console.log('Backend returned status code: ', err.status); console.log('Response body:', err.error); } } ); } getAllFavBooks() { this.myAllfavBooks$ = this.bookService.getFavBookFromStore(101) .mergeMap(book => this.bookService.getBooksByCategoryFromStore(book.category)); // Using subscribe this.bookService.getFavBookFromStore(101).mergeMap(book => { let category = book.category; return this.bookService.getBooksByCategoryFromStore(category); }).subscribe(books => { this.allFavBooks = books; }); } getBookName() { this.favBookName$ = this.bookService.getFavBookFromStore(101).map(book=> book.name); // Using subscribe this.bookService.getFavBookFromStore(101) .map(book=> { if(book.name.length < 15) { return book.name; } else { throw('Length less than 15'); } }) .catch(error => { console.log(error); //return of("Default Name"); throw(error.message || error); }) .subscribe(name=> { this.bookName = name; console.log(name); }, err => { console.log(err); } ); } searchSimilarBooks(id: number) { this.similarBooks$ = this.bookService.getFavBookFromStore(id) .switchMap(book => { let category = book.category; return this.bookService.getBooksByCategoryFromStore(category); }) .catch(err => of([])); // Using subscribe this.bookService.getFavBookFromStore(id) .switchMap(book => { let category = book.category; return this.bookService.getBooksByCategoryFromStore(category); }) .catch(err => of([])) .subscribe(books => { this.similarFavBooks = books; console.log(books); }); } }
<h2>Data using Async Pipe</h2> <b>Book Details</b><br/> <ul> <li *ngFor="let book of allBooks$ | async" > Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </li> </ul> <br/><b>Favorite Book Detail</b><br/> <div *ngIf="favBook$ | async as book; else loading"> Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </div> <ng-template #loading> <img src="assets/images/loading.gif"> </ng-template> <br/><b>Favorite Book Name</b> <div *ngIf="favBookName$ | async as bookName"> Name: {{bookName}} </div> <br/><b>My All Favorite Books</b><br/> <ul> <li *ngFor="let book of myAllfavBooks$ | async" > Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </li> </ul> <br/><b>Search Similar Books for Id</b><br/><br/> <input [(ngModel)]="bookId" (input)="searchSimilarBooks(bookId)"> <ul> <li *ngFor="let book of similarBooks$ | async" > Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </li> </ul> <h2>Data using subscribe</h2> <b>Software Books</b><br/> <ul> <li *ngFor="let book of softBooks" > Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </li> </ul> <br/><b>All Favorite Books</b><br/> <ul> <li *ngFor="let book of allFavBooks" > Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}} </li> </ul>
import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-book></app-book> ` }) export class AppComponent { }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; import { BookComponent } from './book.component'; import { BookService } from './book.service'; //For InMemory testing import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { TestData } from './test-data'; @NgModule({ imports: [ BrowserModule, HttpClientModule, FormsModule, InMemoryWebApiModule.forRoot(TestData) ], declarations: [ AppComponent, BookComponent ], providers: [ BookService ], bootstrap: [ AppComponent ] }) export class AppModule { }
13. Run Application
To run the application, find the steps.1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Install angular-in-memory-web-api@0.5.3
4. Run ng serve using command prompt.
5. Access the URL http://localhost:4200
Find the print screen of the output.

Observable
. Find the link for NgRx examples.
NgRx Tutorials
14. References
RxJS ObservableAngular HttpClient get Example