HTTP in Angular 2
Highlighting the HTTP features in AngularJS 2 for asynchronous web development.
In many modern websites, you would find the search functionality which allows you to search whatever keyword you have written in the whole website. Often the search function allows you to type something in it and see the suggestions (based on what you have written) simultaneously. The Google search engine is the perfect example of it. You may ask how it is giving you the suggestions? What magic is happening behind the page?
It's all in the single word: HTTP! Basically, HTTP allows you to call out to external APIs, meaning you can make requests from the server (through HTTP requests) and get the results (through HTTP responses) in an asynchronous fashion. By asynchronous, I mean when the requests/responses are happening, the client (or the user on the website) doesn't get blocked, and so the results happen in real-time! Contrast it with synchronous processing, which blocks the JavaScript engine.
HTTP can be considered a protocol that allows request-response between a client and server. There are two main HTTP methods: GET and POST. GET requests the data from a specified resource, while the POST submits the data to be processed to a specified resource.
In this article, I am going to show you what is HTTP in AngularJS 2, and how to use it in your application. Let's get started!
HTTP in AngularJS 2 - What it is really about?
In JavaScript, there are usually three methods to dealing with asynchronous code:
- Callbacks - It is a function that is to be executed after another function is executed. In other words, callbacks are the functions that are passed as arguments to other functions. This is very useful in asynchronous programming, because the function that receives the callback can execute the code while the task is happening simultaneously!
- Promises - It allows you to handle a single callback when an async operation is happening.
- Observables - It allows you to pass more than one event and the callback is called for each event.
AngularJS 2 uses Observables and favor it over Promises (which was present in AngularJS 1), so in order to understand HTTP, we need to understand the Observables first. AngularJS 2 uses RxJS (http://reactivex.io/rxjs/) library for the Reactive programming -- which is nothing but a programming with asynchronous data streams! It basically provides a stream of notifications to a caller by issuing a return type known as an Observable. Streams are a sequence of digital data that needs to be transmitted or received.
Promises vs Observables
As I mentioned earlier, Observables are able to handle multiple values over time in contrast to Promises that are only called once and will return a single value. Observables are also cancellable. Promises are not cancellable. As Observables have the ability to handle multiple data over time, it is a great option for working with real-time data where the changes are happening quickly. RxJS not only provides the Observables, but also the accompanying operators like Map, FIlter, Take, Skip, etc.
Promises Example
loadUsers() {
fetch('/api/users').then((response) => {
return response.json();
}).then((data) => {
this.users = data;
}).catch((ex) => {
console.error('Error fetching users', ex);
});
}
Observables Example
this.http.get('/api/cats.json')
.map(res => res.json() || {})
.filter(cat => cat.color == 'orange');
You can see that Observables allow you to stream data through powerful sequence operations!
Wikipedia search and YouTube search
We will create a project that will use the HTTP/Observables to get the JSON data from the Wikipedia/YouTube APIs and show the suggestions whenever a user types something in the search field. We will importing some libraries from '@angular/http', creating some components and services and passing the callbacks in the functions.
So, we want the following functionalities in our application:
- A text/input field that allows the user to type some term in it and expect the suggestions.
- The suggestions will come from the Wikipedia/YouTube API.
- We want this process to happen synchronously, so the user should receive the suggestions the moment they start typing. We can't stop the user from typing, so no blocking of the client is allowed.
We know that Promises only single callback, and so for several callbacks, the client needs to be blocked, therefore, we will use the Observables. I have taken the code inspiration from the ng-book2's YouTube search code and adapted it with the project.
Creating a project
We will create two search areas on our page: Wikipedia search and YouTube search. The task is simple: A user will start typing in the field, and the HTTP will send the requests from the server to the client through different APIs (i.e., Wikipedia API, YouTube API, etc.). Observables will be used. So, start a new project "AngularJS2-HTTP":
$ng new AngularJS2-HTTP
Now, let's create two components:
$ng g component wikipedia-search
$ng g component youtube-search
We will be using the JSON library to set/get the values from the APIs. Http and JsonpModule is provided in "@angular/http". Create a new file "wikipedia.service.ts" inside the wikipedia-search folder and copy the following contents:
wikipedia.service.ts
import { Injectable } from '@angular/core';
import { URLSearchParams, Jsonp, Response, Http, Headers} from '@angular/http';
// We need to import toPromise() from RxJS to convert the Observable into Promise
import 'rxjs/add/operator/toPromise';
@Injectable()
export class WikipediaService {
constructor(private jsonp: Jsonp) {}
searchKey(input: string) {
var search = new URLSearchParams()
search.set('action', 'opensearch');
search.set('search', input);
search.set('format', 'json');
return this.jsonp
.get('http://en.wikipedia.org/w/api.php?callback=JSONP_CALLBACK', { search })
.toPromise()
.then((request) => request.json()[1]);
}
}
Here, we are injecting the Jsonp service (as in dependency injection) and making the SET/GET requests against the wikipedia API with a given search term (which we will take from the user). Then we are calling the toPromise() to kind of convert the Observable<Response> to Promise<Response>. We did this because the core libraries of AngularJS 2 expect the Promise return values. Eventually, we get the Promise<Array<string>> as the return type of our searchKey method.
Copy the following code in "wikipedia-search.component.ts":
wikipedia-search.component.ts
import { Component } from '@angular/core';
import { WikipediaService } from './wikipedia.service';
@Component({
selector: 'wikipedia-search',
templateUrl: './wikipedia-search.component.html'
})
export class WikipediaSearchComponent {
items: Array<string>;
constructor(private wikipediaService: WikipediaService) {}
searchKey(input) {
this.wikipediaService.searchKey(input).then(items => this.items = items);
}
}
Here, we are taking the input from the user and exposing its functionality to our searchKey method. Following is its HTML "wikipedia-search.component.html" content:
wikipedia-search.component.html
<div>
<h2>Wikipedia Search</h2>
<p>Enter something in the field and see the asynchronous results!</p>
<input #input type="text" placeholder="Search" (keyup)="searchKey(input.value)">
<ul>
<li *ngFor="let item of items">{{item}}</li>
</ul>
</div>
Here, we are using *ngFor to looping through the array of items.
Now, paste the following content in "youtube-search.component.ts":
youtube-search.component.ts
import {
Component,
Injectable,
OnInit,
ElementRef,
EventEmitter,
Inject
} from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs';
export var YOUTUBE_API_KEY: string = 'AIzaSyDOfT_BO81aEZScosfTYMruJobmpjqNeEk';
export var YOUTUBE_API_URL: string = 'https://www.googleapis.com/youtube/v3/search';
class SearchResult {
id: string;
title: string;
description: string;
thumbnailUrl: string;
videoUrl: string;
constructor(obj?: any) {
this.id = obj && obj.id || null;
this.title = obj && obj.title || null;
this.description = obj && obj.description || null;
this.thumbnailUrl = obj && obj.thumbnailUrl || null;
this.videoUrl = obj && obj.videoUrl ||
`https://www.youtube.com/watch?v=${this.id}`;
}
}
@Injectable()
export class YouTubeService {
constructor(public http: Http,
@Inject(YOUTUBE_API_KEY) private apiKey: string,
@Inject(YOUTUBE_API_URL) private apiUrl: string) {
}
search(query: string): Observable<SearchResult[]> {
let params: string = [
`q=${query}`,
`key=${this.apiKey}`,
`part=snippet`,
`type=video`,
`maxResults=10`
].join('&');
let queryUrl: string = `${this.apiUrl}?${params}`;
return this.http.get(queryUrl)
.map((response: Response) => {
return (<any>response.json()).items.map(item => {
// console.log("raw item", item); // uncomment if you want to debug
return new SearchResult({
id: item.id.videoId,
title: item.snippet.title,
description: item.snippet.description,
thumbnailUrl: item.snippet.thumbnails.high.url
});
});
});
}
}
export var youTubeServiceInjectables: Array<any> = [
{provide: YouTubeService, useClass: YouTubeService},
{provide: YOUTUBE_API_KEY, useValue: YOUTUBE_API_KEY},
{provide: YOUTUBE_API_URL, useValue: YOUTUBE_API_URL}
];
@Component({
outputs: ['loading', 'results'],
selector: 'search-box',
template: `
<p>Enter something in the field and see the asynchronous results!</p>
<input type="text" class="form-control" placeholder="Search" autofocus>
`
})
export class SearchBox implements OnInit {
loading: EventEmitter<boolean> = new EventEmitter<boolean>();
results: EventEmitter<SearchResult[]> = new EventEmitter<SearchResult[]>();
constructor(public youtube: YouTubeService,
private el: ElementRef) {
}
ngOnInit(): void {
// convert the `keyup` event into an observable stream
Observable.fromEvent(this.el.nativeElement, 'keyup')
.map((e: any) => e.target.value) // extract the value of the input
.filter((text: string) => text.length > 1) // filter out if empty
.debounceTime(250) // only once every 250ms
.do(() => this.loading.next(true)) // enable loading
// search, discarding old events if new input comes in
.map((query: string) => this.youtube.search(query))
.switch()
// act on the return of the search
.subscribe(
(results: SearchResult[]) => { // on sucesss
this.loading.next(false);
this.results.next(results);
},
(err: any) => { // on error
console.log(err);
this.loading.next(false);
},
() => { // on completion
this.loading.next(false);
}
);
}
}
@Component({
inputs: ['result'],
selector: 'search-result',
template: `
<div class="col-sm-6 col-md-3">
<div class="thumbnail">
<img src="{{result.thumbnailUrl}}">
<div class="caption">
<h3>{{result.title}}</h3>
<p>{{result.description}}</p>
<p><a href="{{result.videoUrl}}"
class="btn btn-default" role="button">Watch Now</a></p>
</div>
</div>
</div>
`
})
export class SearchResultComponent {
result: SearchResult;
}
@Component({
selector: 'youtube-search',
templateUrl: './youtube-search.component.html'
})
export class YoutubeSearchComponent {
results: SearchResult[];
updateResults(results: SearchResult[]): void {
this.results = results;
}
}
This is the most important code section in YouTube search component. We have divided the task into three components: SearchBox, SearchResultComponent and YoutubeSearchComponent. We also used YouTubeService to pass as injectable in SearchBox's constructor.
Now, paste the following content in "youtube-search.component.html":
youtube-search.component.html
<div class='container'>
<div class="page-header">
<h2>YouTube Search
</h2>
</div>
<div class="row">
<div class="input-group input-group-lg col-md-12">
<search-box
(loading)="loading = $event"
(results)="updateResults($event)"
></search-box>
</div>
</div>
<div class="row">
<search-result
*ngFor="let result of results"
[result]="result">
</search-result>
</div>
</div>
Here, we are simply passing the values to the methods in our components.
Now paste the following code in "app.module.ts" and "app.component.html":
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { JsonpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { WikipediaSearchComponent } from './wikipedia-search/wikipedia-search.component'
import { WikipediaService } from './wikipedia-search/wikipedia.service';
import { youTubeServiceInjectables } from './youtube-search/youtube-search.component';
import { HttpModule } from '@angular/http';
import {
YoutubeSearchComponent,
SearchBox,
SearchResultComponent
} from './youtube-search/youtube-search.component';
@NgModule({
imports: [BrowserModule, JsonpModule, HttpModule],
declarations: [AppComponent, WikipediaSearchComponent, YoutubeSearchComponent,
SearchBox,
SearchResultComponent],
providers: [WikipediaService, youTubeServiceInjectables],
bootstrap: [AppComponent]
})
export class AppModule {}
app.component.html
<h2>AngularJS 2 HTTP Example</h2>
<wikipedia-search></wikipedia-search>
<youtube-search></youtube-search>
You don't need to touch the "app.module.ts". In the above, we did nothing but added the required components/modules in the directives, which should be self-explanatory if you have been following my previous AngularJS 2 tutorials.
If you do "ng serve" now, you will see the following:
I have uploaded the repository to Github (https://github.com/danyalzia/AngularJS2-HTTP) in case you have trouble following the code.
Conclusion
Thus so far, I have shown you what HTTP is really about in AngularJS 2, and how it can make your life easier! HTTP has come so far in its approach to typical problems. With the advent of Observables, you can see how easy it is to do several tasks at once with as little code as possible!
If you have any questions, then please ask in the comments section below!
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment