Promise vs Observable in Angular with (.subscribe, .then , .pipe).
######################################################################################## Advance best Explanation definitely Read below ############################################################################### #########
we will discuss the differences between promises and observables. In Angular 2, to work with asynchronous data we can use either Promises or Observables. In our previous videos in this series, we discussed using both Observables and Promises. There are several differences between Promises and Observables. In this video let’s discuss these differences.
As a quick summary the differences are shown in the table below
PromiseObservableEmits a single valueEmits multiple values over a period of timeNot LazyLazy. An Observable is not called until we subscribe to the ObservableCannot be cancelledCan be cancelled using the unsubscribe() methodObservable provides operators like map, forEach, filter, reduce, retry, retryWhen etc.
A Promise emits a single value where as an Observable emits multiple values over a period of time. You can think of an Observable like a stream which emits multiple items over a period of time and the same callback function is called for each item emitted. So with an Observable we can use the same API to handle asynchronous data whether that data is emitted as a single value or multiple values over a period of time.
A Promise is not lazy where as an Observable is Lazy. Let’s prove this with an example. Consider this method getEmployeeByCode() in employee.service.ts. Notice this method returns an Observable.
getEmployeeByCode(empCode: string): Observable<IEmployee> {
return this._http.get(“http://localhost:31324/api/employees/" + empCode)
.map((response: Response) => <IEmployee>response.json())
.catch(this.handleError);
}
Here is the consumer code of the above service. In our example, this code is in employee.component.ts in ngOnInit() method. Notice we are subscribing to the Observable using the subscribe() method. An Observable is lazy because, it is not called and hence will not return any data until we subscribe using the subscribe() method. At the moment we are using the subscribe() method. So the service method should be called and we should receive data.
ngOnInit() {
let empCode: string = this._activatedRoute.snapshot.params[‘code’];
this._employeeService.getEmployeeByCode(empCode)
.subscribe((employeeData) => {
if (employeeData == null) {
this.statusMessage =
‘Employee with the specified Employee Code does not exist’;
}
else {
this.employee = employeeData;
}
},
(error) => {
this.statusMessage =
‘Problem with the service. Please try again after sometime’;
console.error(error);
});
}
To prove this
1. Launch Browser developer tools by pressing F12 while you are on the browser.
2. Navigate to /src/employees/emp101
3. Click on the Network Tab
4. In the Filter textbox, type “api/employees”
5. At this point, you should only see the request issued to EmployeeService in the table below
6 Hover the mouse over “emp101” under “Name” column in the table
7. Notice the request to employee service (/api/employees/emp101) is issued
Instead of hovering over the request, if you click on it, you can see the response data as shown below. Make sure you select the “preview” tab.
Now in employee.component.ts file, comment the subscribe() method code block as shown below. Notice we are still calling the getEmployeeByCode() method of the EmployeeService. Since we have not subscribed to the Observable, a call to the EmployeeService will not be issued over the network.
ngOnInit() {
let empCode: string = this._activatedRoute.snapshot.params[‘code’];
this._employeeService.getEmployeeByCode(empCode)
//.subscribe((employeeData) => {
// if (employeeData == null) {
// this.statusMessage =
// ‘Employee with the specified Employee Code does not exist’;
// }
// else {
// this.employee = employeeData;
// }
//},
//(error) => {
// this.statusMessage =
// ‘Problem with the service. Please try again after sometime’;
// console.error(error);
//});
}
With the above change in place, reload the web page. Notice no request is issued over the network to the EmployeeService. You can confirm this by looking at the network tab in the browser developer tools. So this proves that an Observable is lazy and won’t be called unless we subscribe using the subscribe() method.
Now, let’s prove that a Promise is NOT Lazy. I have modified the code in employee.service.ts to return a Promise instead of an Observable. The modified code is shown below.
getEmployeeByCode(empCode: string): Promise<IEmployee> {
return this._http.get(“http://localhost:31324/api/employees/" + empCode)
.map((response: Response) => <IEmployee>response.json())
.toPromise()
.catch(this.handlePromiseError);
}
Here is the consumer code of the above service. In our example, this code is in employee.component.ts in ngOnInit() method.
ngOnInit() {
let empCode: string = this._activatedRoute.snapshot.params[‘code’];
this._employeeService.getEmployeeByCode(empCode)
.then((employeeData) => {
if (employeeData == null) {
this.statusMessage =
‘Employee with the specified Employee Code does not exist’;
}
else {
this.employee = employeeData;
}
},
(error) => {
this.statusMessage =
‘Problem with the service. Please try again after sometime’;
console.error(error);
});
}
Because a promise is eager(not lazy), calling employeeService.getEmployeeByCode(empCode) will immediately fire off a request across the network to the EmployeeService. We can confirm this by looking at the network tab in the browser tools.
Now you may be thinking, then() method in this case is similar to subscribe() method. If we comment then() method code block, will the service still be called. The answer is YES. Since a Promise is NOT LAZY, Irrespective of whether you have then() method or not, calling employeeService.getEmployeeByCode(empCode) will immediately fire off a request across the network to the EmployeeService. You can prove this by commenting then() method code block and reissuing the request.
################################################################################################################################################
http://csharp-video-tutorials.blogspot.com/2017/09/angular-promises-vs-observables.htmlhttps://medium.com/@mpodlasin/promises-vs-observables-4c123c51fe13
Examples =: https://github.com/jpssasadara/web_Socket_Epic_POS_Final/tree/master/Fhttps://github.com/jpssasadara/Ex_Hub/tree/master/1.5%20Ex_hub
##################################################################################################################################################
Angular Observable pipe
his page will walk through Angular Observable pipe
example. RxJS pipe
is used to combine functional operators into a chain. pipe
is an instance method of Observable
as well as a standalone RxJS function. pipe
can be used as Observable.pipe
or we can use standalone pipe
to combine functional operators. The declaration of pipe
is as following.
public pipe(operations: ...*): Observable
pipe
accepts operators as arguments such as filter
, map
, mergeScan
etc with comma separated and execute them in a sequence they are passed in as arguments and finally returns Observable
instance. To get the result we need to subscribe
it. Now let us discuss the complete example.
Contents
- Technologies Used
- Import RxJS 6 Observable and Operators
- Observable.pipe with filter, map and scan
- Using Standalone pipe
- Error Handling: pipe with retry and catchError
- pipe with mergeMap
- pipe with switchMap
- Complete Example
- Run Application
- References
- Download Source Code
echnologies Used
Find the technologies being used in our example.
1. Angular 7.0.0
2. Angular CLI 7.0.3
3. TypeScript 3.1.1
4. Node.js 10.3.0
5. NPM 6.1.0
6. RxJS 6.3.3
7. In-Memory Web API 0.6.1
Import RxJS 6 Observable and Operators
In RxJS 6, Observable
and operators are imported as following.
1. Import Observable
and of
from rxjs
.
import { Observable, of } from 'rxjs';
pipe
will be accessed using Observable.pipe
. To use standalone pipe
, we can also import it.
import { pipe } from 'rxjs';
2. Operators are imported from rxjs/operators
. Find some operators to import it.
import { mergeMap, switchMap, retry,
map, catchError, filter, scan } from 'rxjs/operators';
Observable.pipe with filter, map and scan
Here we will use Observable.pipe
to combine functions. Suppose we have a service method to get numbers.
getNumbers(): Observable<number> {
return of(1, 2, 3, 4, 5, 6, 7);
}
1. Find the code to use pipe
with filter
operator.
calculateNumbers() {
this.bookService.getNumbers().pipe(
filter(n => n % 2 === 1)
)
.subscribe(result => console.log(result));
}
Output will be 1,3,5,7.
We can observe that we are passing filter
operator within pipe
. Final result of pipe
after executing filter
, will be instance of Observable
. To get the result we need to subscribe it.
2. Find the code to use pipe
with filter
and map
operators.
calculateNumbers() {
this.bookService.getNumbers().pipe(
filter(n => n % 2 === 1),
map(n => n + 10)
)
.subscribe(result => console.log(result));
}
Output will be 11, 13, 15, 17.
In the above code we are passing filter
and map
operators in pipe
function as arguments. filter
and map
will execute in the order they are passed in. On the Observable
instance returned by getNumbers()
, filter
operator will be executed and on the Observable
instance returned by filter
, map
operator will be executed and the final result returned by pipe
will be the result returned by last operator i.e. map
in above code.
3. Find the code to use pipe
with filter
, map
and scan
operators.
calculateNumbers() {
this.bookService.getNumbers().pipe(
filter(n => n % 2 === 1),
map(n => n + 10),
scan((sum, n) => sum + n)
)
.subscribe(result => console.log(result));
}
The output will be 11, 24, 39, 56.
In the above code we are using three operators with pipe
i.e. filter
, map
and scan
. They will execute in the order they are passed in as arguments. In the above code, first filter
will execute then map
and then scan
. Final output returned by pipe
will be Observable
returned by scan
.
Using Standalone pipe
To use pipe
standalone, we need to import it as following.
import { pipe } from 'rxjs';
Now find the code snippet to use it.
calculateNumbers() {
//Create a function to accept Observable instance
const calculationFun = pipe(
filter((n: number) => n % 2 === 1),
map((n: number) => n + 10),
scan((sum, n) => sum + n)
); //Instantiate response Observable
const calculation$ = calculationFun(this.bookService.getNumbers()); //Subscribe the Observable instance
calculation$.subscribe(result => console.log(result));
}
Output will be 11, 24, 39, 56.
Error Handling: pipe with retry and catchError
retry
operator reties to access URL for the given number of time before failing. catchError
has been introduced in RxJS 6. catchError
is used in place of catch
. Find the code to use pipe
with retry
and catchError
operator.
getBooks() {
this.allBooks$ = this.bookService.getBooksFromStore().pipe(
retry(3),
map(books => {
if (books.length < 5) {
throw new Error('Not enough books');
}
return books;
}),
catchError(err => {
console.error(err);
return of([]);
})
);
}
We can also subscribe the Observable
instance.
getBooks() {
this.bookService.getBooksFromStore().pipe(
retry(3),
map(books => {
if (books.length < 5) {
throw new Error('Not enough books');
}
return books;
}),
catchError(err => {
console.error(err);
return of([]);
})
)
.subscribe(books => this.allBooks = books);
}
pipe with mergeMap
Find the code snippet to use pipe
with mergeMap
operator.
getAllFavBooks() {
this.bookService.getFavBookFromStore(101).pipe(
mergeMap(book => {
let category = book.category;
return this.bookService.getBooksByCategoryFromStore(category);
})
).subscribe(books => {
this.allFavBooks = books;
});
}
pipe with switchMap
Find the code snippet to use pipe
with switchMap
operator.
searchSimilarBooks(id: number) {
this.bookService.getFavBookFromStore(id).pipe(
switchMap(book => {
let category = book.category;
return this.bookService.getBooksByCategoryFromStore(category);
}),
catchError(err => of([]))
)
.subscribe(books => {
this.similarBooks = books;
console.log(books);
});
}
Complete Example
Here we will provide complete demo to use pipe
.
book.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable, of, pipe } from 'rxjs';
import { mergeMap, switchMap, retry,
map, catchError, filter, scan } from 'rxjs/operators';
import { BookService } from './book.service';
import { Book } from './book';@Component({
selector: 'app-book',
templateUrl: './book.component.html'
})
export class BookComponent implements OnInit {
softBooks: Book[];
allFavBooks: Book[];
similarBooks: Book[]; constructor(private bookService: BookService) { }
ngOnInit() {
this.calculateNumbers1();
this.calculateNumbers2();
this.getSoftBooks();
this.getAllFavBooks();
} //Using Observable.pipe with filter, map and scan
calculateNumbers1() {
this.bookService.getNumbers().pipe(
filter(n => n % 2 === 1),
map(n => n + 10),
scan((sum, n) => sum + n)
)
.subscribe(result => console.log(result));
} //Using Standalone pipe
calculateNumbers2() {
//Create a function to accept Observable instance
const calculationFun = pipe(
filter((n: number) => n % 2 === 1),
map((n: number) => n + 10),
scan((sum, n) => sum + n)
); //Instantiate response Observable
const calculation$ = calculationFun(this.bookService.getNumbers()); //Subscribe the Observable instance
calculation$.subscribe(result => console.log(result));
}
//Using retry() and catchError operator
getSoftBooks() {
this.bookService.getBooksFromStore().pipe(
retry(3),
map(books => {
if (books.length < 5) { //It will throw error in console
throw new Error('Not enough books');
}
return books;
}),
catchError(err => {
console.error(err);
return of([]);
})
)
.subscribe(books => this.softBooks = books);
} //Using mergeMap
getAllFavBooks() {
this.bookService.getFavBookFromStore(101).pipe(
mergeMap(book => {
let category = book.category;
return this.bookService.getBooksByCategoryFromStore(category);
})
).subscribe(books => {
this.allFavBooks = books;
});
}
//Using switchMap
searchSimilarBooks(id: number) {
this.bookService.getFavBookFromStore(id).pipe(
switchMap(book => {
let category = book.category;
return this.bookService.getBooksByCategoryFromStore(category);
}),
catchError(err => of([]))
)
.subscribe(books => {
this.similarBooks = books;
console.log(books);
});
}
}
book.component.html
<b>All Favorite Books</b><br/>
<ul>
<li *ngFor="let book of allFavBooks" >
Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}}
</li>
</ul><b>Search Similar Books</b><br/><br/>
ID: <input [(ngModel)]="bookId" (input)="searchSimilarBooks(bookId)"><ul>
<li *ngFor="let book of similarBooks" >
Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}}
</li>
</ul>
book.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { Book } from './book';@Injectable({
providedIn: 'root'
})
export class BookService {
bookUrl = "/api/books"; constructor(private http: HttpClient) { }
getNumbers(): Observable<number> {
return of(1, 2, 3, 4, 5, 6, 7);
}
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);
}
}
book.ts
export interface Book {
id: number;
name: string;
category: string;
}
app.component.ts
import { Component } from '@angular/core';@Component({
selector: 'app-root',
template: `
<app-book></app-book>
`
})
export class AppComponent {
}
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 };
}
}
app.module.ts
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: [
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
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.6.1
4. Run ng serve using command prompt.
5. Access the URL http://localhost:4200
Find the print screen of the output.
References
RxJS Observable
Angular: The RxJS library
Pipeable Operators
Download Source Code
angular-observable-pipe.zip
Creating pipe ==> https://alligator.io/angular/custom-pipes-angular/