Angular 2 - SPLessons

Angular 2 Data using HTTP

Home > Lesson > Chapter 11
SPLessons 5 Steps, 3 Clicks
5 Steps - 3 Clicks

Angular 2 Data using HTTP

Angular 2 Data using HTTP

shape Introduction

Most Angular applications obtain data using Http. The application issues an Http get request to a web service. That web service retrieves the data, often using a database, and returns it to the application in an Http response. The application then processes that data. Following are the major concepts covered in this chapter.
  • Observables and Reactive Extensions
  • Sending an Http Request
  • Exception Handling
  • Subscribing to an Observable

Observables and Reactive Extensions

shape Description

Observables helps to manage asynchronous data, such as data coming from a back-end service. Observables treat events as a collection. One can think of an observable as an array whose items arrive asynchronously over time. On the timeline to the left, the first marble comes in, then the second, and sometime later the third. Observables are a proposed feature for ES 2016, the next version of JavaScript. To use observables now, Angular uses a third-party library called reactive extensions. Observables are used within Angular itself, including Angular's event system and its Http client service.

Observable Operators

shape Description

Observables allows to manipulate sets of events with operators. Operators are methods on observables that compose new observables. Each operator transforms the source observable in some way. Operators do not wait for all of the values and process them at once. Rather, operators on observables process each value as it is emitted. Some examples of operators include map, filter, take, and merge. Data sequences can take many forms, such as a response from a backend web service, a set of system notifications, or a stream of events, such as user input. In this diagram, the stream contains three elements. Reactive extensions represent a data sequence as an observable sequence, commonly just called an observable. A method in the code can subscribe to an observable to receive asynchronous notifications as new data arrives. The method can then react as data is pushed to it. The method is notified when there is no more data or when an error occurs. Observables support operators, such as the map operator. The map operator allows to transform the incoming data. The argument to the map operator is an arrow function that says to take each data item and transform it to 10 times its value. So when received one, it is mapped to 10. When received two, it is mapped to 20, and so on. This depiction is called a marble diagram and is helpful for visualizing observable sequences and is available at Rxmarbles website.

Promise vs Observables

shape Description

Observables are different from promises in several ways.
Promise Observable
A promise returns a single future value. An observables emits multiple asynchronous values over time.
A promise is not lazy. By the time is user have a promise, it's on its way to being resolved. An observable is lazy by default. Observables will not emit values until they are subscribed to.
A promise is not cancellable. It is resolved or rejected and only once. An observable can be cancelled by unsubscribing. Plus an observable supports map, filter, reduce, and similar operators.

Sending an HTTP Request

shape Description

One can often encapsulate the data access for our application into a data service that can be used by any component or other service that needs it. In the sample application product management instead of using the hard-coded list of products one can send an Http request to get the products from a backend web server. Angular provides an Http service that allows users to communicate with a backend web server. Using the familiar Http request and response protocol. For example, one can call a get method of the Http service, which in turn sends a get request to the web server. The web server response is returned from the Http service to our product data service as an observable as shown in the figure below. The snippet code below is the product service built in sample application modified to retrieve the products using Angular’s Http service. Product.service.ts [c] ... import { Http } from '@angular/http'; @Injectable() export class ProductService { private _productUrl = 'www.myWebService.com/api/products'; constructor(private _http: Http) { } getProducts() { return this._http.get(this._productUrl); } } [/c] Here in the code, a URL is specified to the products on the web server. The Url defines where to send the Http requests, Note that the Url in the code is shown for illustration purposes only and is a real URL. Next a constructor is added, the constructor is used to inject the dependencies. In this case, Angular’s Http service is required so injected the dependencies, the syntax creates a private_http variable and assigns the injected service instance to that variable, and since the variable is strongly typed to Http, imported the Http from the @angular/http package . In some cases the injectable decorator was optional but in this case, the product service has an injected dependency, the injectable decorator is required.  One can inject a service in as a dependency, but need to register that service's provider with Angular's injector. The snippet code below is the app module built in sample application. app.module.ts [c] ... import { HttpModule } from '@angular/http'; @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule ], declarations: [ AppComonent, ProductListComponent, ProductFilterPipe, StarComponent ], bootstrap: [AppComponent ] }) export class AppModule { } [/c] The Http service provider registration is done in the Http module so include the @angular/http package as shown in the code above. In order to include the features of the external package in the application add the imports array of the application’s Angular module, AppModule. The declarations array is used for declaring components, directives and pipes that belong to the module, the imports array is for pulling in the external modules, so the import array in the code is used to pull the external HttpModule. Go back to the product service code in the sample application and add the observable response as shown in the code below. [c] ... import { Http, Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; @Injectable() export class ProductService { private _productUrl = 'www.myWebService.com/api/products'; constructor(private _http: Http) { } getProducts(): Observable<Response> { return this._http.get(this._productUrl) .map((response: Response) => <IProduct[]>response.json()); } } [/c] In the code the getProducts is used to inject Http service instance and the get method is called, passing in the desired URL. The Http service then sends a get request using the specified URL. Since using strong typing, a function return value should be given. The getProducts method returns an observable of Response. Observables take advantage of generics to define the type of data it is observing in the observable sequence. In this case, it's the Http Response. It's important to note that Http calls are single asynch operations, meaning that the observable sequence contains only one element, which is the Http Response object. And one need to import Response from the @angular/http package and observable from rxjs/Observable. The components, such as the product list component, are expecting to receive a list of products, not an Http response. So one need to translate the Response object to an array of products which can be done using the map operator. The map operator takes the raw Http Response object returned from the Http get method and translates it into an array of products. The argument to the map method is an arrow function that transforms the response to a JSON object. Use a casting operator to cast the JSON object to an array of products. So now the getProducts returns an observable of IProduct array, which is much more useful for the components. Note that depending on the server, the JSON result may be wrapped in another property, such as a data property. If that is the case, use response.json.data. To use the map operator, one need to load it using an import statement. Using the import statement as shown in the code is a bit unusual. It tells the module loader to load the library but actually doesn't import anything. When a library loaded, its JavaScript is executed, and for this particular library, executing the JavaScript loads the map operator. So this particular syntax imports the module for its side-effects only, without actually importing any of its features.

shape Example

In order to add the Http to the product service open the application Angular module, AppModule, as demonstrated above the Angular registers its Http service provider in an Angular module called HttpModule. In this case, import the HttpModule which is located in the @angular/http package and then pull the module into the application by adding the HttpModule to the imports array as shown in the code below. app.module.ts [c] import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; import { ProductListComponent } from './products/product-list.component'; import { ProductFilterPipe } from './products/product-filter.pipe'; import { StarComponent } from './shared/star.component'; @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule ], declarations: [ AppComponent, ProductListComponent, ProductFilterPipe, StarComponent ], bootstrap: [ AppComponent ] }) export class AppModule { } [/c] Now, open the product data service created in the sample application with all the hard-coded data, modify the product service to get the data using the Http as shown in the code below. product.service.ts [c] import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import { IProduct } from './product'; @Injectable() export class ProductService { private _productUrl = 'api/products/products.json'; constructor(private _http: Http){} getProducts(): Observable<IProduct[]> { return this._http.get(this._productUrl) .map((response: Response) => <IProduct[]> response.json()); } } [/c] In the above code, the import statements for Http client service and the Http Response from @angular/http and the observable is imported from the rxjs/Observable. The map operator is imported using the special syntax which loads the operator without actually importing anything. Add a constructor and specify the parameter, angular will then inject the Http client service instance into the variable. Give the location for identifying the web server using the URL, in this case the URL is not a valid web server and is taken from the local JSON file. So, in order to change this code to work against a web server, simply change the URL to point to an appropriate web server and write the code for server-side. Finally delete the hard-coded products data from the getProducts method and call the http.get method instead of passing in the defined URL, and use the map operator to map the Http response to a product array using the response.json and change the return type as observable of IProduct array.

Exception Handling

shape Description

There are many things that can go wrong when communicating with a back-end service. Everything from an invalid request to a lost connection. So in order to overcome this add some exception handling as shown in the code below. product.service.ts [c] ... import 'rxjs/add/operrator/do'; import 'rxjs/add/operator/catch'; ... getProducts(): Observable<IProduct[]> { return this._http.get(this._productUrl) .map((response: Response) => <IProduct[]>response.json()) .do(data => console.log('All: ' + JSON.stringify(data))) .catch(this.handleError); } private habdleError(error: Response) { } [/c] Here in the code, a catch is added and passes the name of error-handling method. The error-handling method gets one parameter, the error: Response object. In that method, one can handle the error as appropriate and can send the error information to the remote logging infrastructure or throw an error to the calling code. One more thing to be noticed in the code, the observable has a do operator that allows us to peek at the data returned from the server without disrupting the flow. One can use this operator when debugging to log data to the console, for example. Notice one can also need two more import statements as shown in the code, one for each additional operator are used.

shape Example

In order to add the exceptional handling, first add the appropriate imports to the code and use the catch operator to call a local handleError method as shown in the code below. product.service.ts [c] import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/map'; import { IProduct } from './product'; @Injectable() export class ProductService { private _productUrl = 'api/products/products.json'; constructor(private _http: Http){} getProducts(): Observable<IProduct[]> { return this._http.get(this._productUrl) .map((response: Response) => <IProduct[]> response.json()) .do(data => console.log('All: ' + JSON.stringify(data))) .catch(this.handleError); } private handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || 'Server error'); } } [/c] In the local handleError method one can log or handle errors any way as per need. Here in this case, it is simply logged to the console and throw an error to the calling code. The do operator lets one to peek at the response data. The JSON.stringify is used to display that data in a very nice format.

Subscribing to an Observable

shape Description

If one familiar with promises, can know receiving the result of the promise, X in this illustration, by calling then. Then takes two arguments, a value function that is called when the promise completes successfully and an error function to handle any error condition. If one define X as an observable instead, simply replace the then with subscribe. Since observables handle multiple values over time, the value function is called for each value the observable emits. In some cases, to know when the observable completes, so observables provide an optional third handler that is executed on completion. The subscribe function returns a subscription. One can use that subscription to call unsubscribe and cancel the subscription if needed. Now that the product data service is returning an observable, any class that needs product data, such as our product list component, can call the service and subscribe to the returned observable as show in the code below. [c] ngOnIntit(): void { this._productService.getProducts() .subscribe( products => this.products = products, error => this.errorMessage = <any>error); } [/c] Observables are lazy. An observable doesn't start emitting values until subscribe is called, so this line of code calls the product data service getProducts method and kicks off the Http get request. It is then set up to asynchronously receive data and notifications from the observable. The first function passed to the subscribe method specifies the action to take whenever the observable emits an item. The method parameter is that emitted item. Since Http calls are single asynch operations, only one item is emitted, which is the Http response object that was mapped to the product array in the service. So the parameter is the array of products. This code then sets the local products property to the returned array of products. The second function is executed if the observable fails. In this example, it sets a local error message variable to the returned error.

shape Example

In the sample application open the product list component, as the product service changed to return an observable, one cannot assign the result to the product property directly. But can subscribe to the returned observable as shown in the code below. product-list.component.ts [c] import { Component, OnInit } from '@angular/core'; import { IProduct } from './product'; import { ProductService } from './product.service'; @Component({ selector: 'pm-products', templateUrl: 'app/products/product-list.component.html', styleUrls: ['app/products/product-list.component.css'] }) export class ProductListComponent implements OnInit{ pageTitle: string = 'Product List!'; imageWidth: number = 50; imageMargin: number = 2; showImage: boolean = false; listFilter: string; errorMessage: string; products: IProduct[]; constructor(private _productSerive: ProductService){ } toggleImage(): void{ this.showImage = !this.showImage; } ngOnInit(): void { this._productSerive.getProducts() .subscribe(products => this.products = products, error => this.errorMessage = <any>error); } onRatingClicked(message: string): void { this.pageTitle = 'Product List: ' + message; } } [/c] When the observable emits the data, set the product property to the returned array of products. And since that product property is bound in the components template, the retrieved data just appears in the view. To handle any errors, add an error message property. If the request for products fails, error message property is set to the error. The any in syntax is a casting operator.  In this case, casting the error returned from the observable to the any data type.

shape Output

Check the output in browser, the products list is displayed and is retrived from the product.json file as shown in the image below. The do added to the Http get pipeline, displays the retrieved data. Open the F12 developer tools, one can see the data in the console as shown in the image below

Summary

shape Key points

  • To setup Http add HttpModule to the imports array of one of the application’s Angular Modules.
  • Observables helps to manage asynchronous data, such as data coming from a back-end service.
  • Exceptional handling is used to overcome the issues while communicating with a back-end service.