Both Promises and Observables provide abstractions to help us handle the asynchronous nature of applications. Günter and @Relu clearly point out the differences between them.
Since the code snippets are lengthy, let's use the examples below to understand them more clearly.
Thanks to Christoph Burgdorf's excellent article here
Angular uses Rx.js Observables instead of Promises for handling HTTP.
Suppose you are building a search functionality that displays results immediately as you type. This sounds familiar, but the task brings many challenges.
- We do not want to access the server endpoint every time the user presses a key. It should not flood the server with numerous HTTP requests. Essentially, we only want to trigger it when the user stops typing, not on every keypress.
- Do not use the same query parameters for subsequent requests to the search endpoint.
- Handle unordered responses. When handling multiple requests simultaneously, we must consider the possibility of them returning in an unexpected order. Imagine we first type Computer, stop, a request is sent, then we type car, stop, another request is sent. Now we have two requests in progress. Unfortunately, the request carrying the Computer results returns after the request carrying the car results.
This demo only includes two files: app.ts and wikipedia-service.ts. However, in the real world, we would likely further split these components.
Below is a Promise-based implementation that does not handle any of the described edge cases.
wikipedia-service.ts
import { Injectable } from '@angular/core';
import { URLSearchParams, Jsonp } from '@angular/http';
@Injectable()
export class WikipediaService {
constructor(private jsonp: Jsonp) {}
search (term: string) {
var search = new URLSearchParams();
search.set('action', 'opensearch');
search.set('search', term);
search.set('format', 'json');
return this.jsonp
.get('http://en.wikipedia.org/w/api.php?callback=JSONP_CALLBACK', { search })
.toPromise()
.then((response) => response.json()[1]);
}
}
We are injecting the service to make a GET request to the Wikipedia API using Jsonp with the given search term. Note that we call toPromise() to convert the Observable<Response> to a Promise<Response>, and finally return a Promise<Array<string>>.
app.ts
// check the plnkr for the full list of imports
import {...} from '...';
@Component({
selector: 'my-app',
template: `
<div>
<h2>Wikipedia Search</h2>
<input #term type="text" (keyup)="search(term.value)">
<ul>
<li *ngFor="let item of items">{{item}}</li>
</ul>
</div>
`
})
export class AppComponent {
items: Array<string>;
constructor(private wikipediaService: WikipediaService) {}
search(term) {
this.wikipediaService.search(term)
.then(items => this.items = items);
}
}
Here, there's not much surprise. We inject WikipediaService into the template and expose its functionality via the search method. The template simply binds to keyup and calls search(term.value).
We unwrap the Promise returned by the search method of WikipediaService and expose it as a simple string array to the template for us to loop through with *ngFor and build a list.
See the Promise-based implementation example on Plunker
Observables truly shine where
Let's change the code so that we no longer hit the endpoint on every keypress, but only when the user stops typing after a 400 ms pause
To reveal such superpowers, we first need to get an Observable<string> with the search term from user input. We can leverage Angular's formControl directive instead of manually binding to the keyup event. To use this directive, we first need to import ReactiveFormsModule into our application module.
app.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { JsonpModule } from '@angular/http';
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [BrowserModule, JsonpModule, ReactiveFormsModule]
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
After importing, we can use formControl in the template and set it to the name 'term'.
<input type="text" [formControl]="term"/>
In the component, we create a FormControl instance from @angular/forms and expose it as a field named 'term'.
Behind the scenes, term automatically exposes an Observable<string> as the valueChanges property. Now that we have an Observable<string>, we can overcome user input by calling debounceTime(400) on our Observable. This will return a new Observable<string> that emits a new value only when no new value appears within 400 ms.
export class App {
items: Array<string>;
term = new FormControl();
constructor(private wikipediaService: WikipediaService) {
this.term.valueChanges
.debounceTime(400) // wait for 400 ms pause in events
.distinctUntilChanged() // ignore if next search term is same as previous
.subscribe(term => this.wikipediaService.search(term).then(items => this.items = items));
}
}
If our application already displays results, sending another search request wastes resources. To achieve the desired behavior, we simply call distinctUntilChanged immediately after debounceTime(400).
See the Observable implementation example on Plunker
For handling unordered responses, see the full article here
As for my use of HTTP in Angular, I agree that using Observables instead of Promises has no significant difference in normal use cases. These advantages are not really relevant in practice. I hope to see some advanced use cases in the future:)
Learn more