HTTP Client

3.1

Send feedback

HTTP is the primary protocol for browser/server communication.

The WebSocket protocol is another important communication technology; it isn’t covered in this page.

Modern browsers support two HTTP-based APIs: XMLHttpRequest (XHR) and JSONP. A few browsers also support Fetch.

The Dart http library simplifies application programming with the XHR and JSONP APIs.

A live example illustrates these topics.

Demos

This page describes server communication with the help of the following demos:

The root AppComponent orchestrates these demos:

lib/app_component.dart

import 'package:angular2/angular2.dart'; import 'src/toh/hero_list_component.dart'; import 'src/wiki/wiki_component.dart'; import 'src/wiki/wiki_smart_component.dart'; @Component( selector: 'my-app', template: ''' <hero-list></hero-list> <my-wiki></my-wiki> <my-wiki-smart></my-wiki-smart> ''', directives: const [ HeroListComponent, WikiComponent, WikiSmartComponent ]) class AppComponent {}

Providing HTTP services

First, configure the application to use server communication facilities.

The Dart BrowserClient client communicates with the server using a familiar HTTP request/response protocol. The BrowserClient client is one of a family of services in the Dart http library.

Before you can use the BrowserClient client, you need to register it as a service provider with the dependency injection system.

Read about providers in the Dependency Injection page.

Register providers using the bootstrap() method:

web/main.dart (v1)

import 'package:angular2/angular2.dart'; import 'package:angular2/platform/browser.dart'; import 'package:server_communication/app_component.dart'; void main() { bootstrap(AppComponent, [ provide(BrowserClient, useFactory: () => new BrowserClient(), deps: []) ]); }

Actually, it is unnecessary to include BrowserClient in the list of providers. But as is mentioned in the Angular Dart Transformer wiki page, the template compiler generates dependency injection code, hence all the identifiers used in DI have to be collected by the Angular transformer so that the libraries containing these identifiers can be transformed.

Unless special steps are taken, Dart libraries like http are not transformed. To ensure that the BrowserClient identifier is available for DI, you must add a resolved_identifiers parameter to the angular2 transformer in pubspec.yaml:

pubspec.yaml (transformers)

transformers: - angular2: entry_points: web/main.dart resolved_identifiers: BrowserClient: 'package:http/browser_client.dart' Client: 'package:http/http.dart' - dart_to_js_script_rewriter web: compiler: debug: dartdevc

The Tour of Heroes HTTP client demo

The first demo is a mini-version of the tutorial’s “Tour of Heroes” (ToH) application. This version gets some heroes from the server, displays them in a list, lets the user add new heroes, and saves them to the server. The app uses the Dart BrowserClient client to communicate via XMLHttpRequest (XHR).

It works like this:

ToH mini app

This demo has a single component, the HeroListComponent. Here’s its template:

lib/src/toh/hero_list_component.html

<h1>Tour of Heroes</h1> <h3>Heroes:</h3> <ul> <li *ngFor="let hero of heroes">{{hero.name}}</li> </ul> <label>New hero name: <input #newHeroName /></label> <button (click)="addHero(newHeroName.value); newHeroName.value=''">Add Hero</button> <p class="error" *ngIf="errorMessage != null">{{errorMessage}}</p>

It presents the list of heroes with an ngFor. Below the list is an input box and an Add Hero button where you can enter the names of new heroes and add them to the database. A template reference variable, newHeroName, accesses the value of the input box in the (click) event binding. When the user clicks the button, that value is passed to the component’s addHero method and then the event binding clears it to make it ready for a new hero name.

Below the button is an area for an error message.

The HeroListComponent class

Here’s the component class:

lib/src/toh/hero_list_component.dart (class)

class HeroListComponent implements OnInit { final HeroService _heroService; String errorMessage; List<Hero> heroes = []; HeroListComponent(this._heroService); Future<Null> ngOnInit() => getHeroes(); Future<Null> getHeroes() async { try { heroes = await _heroService.getHeroes(); } catch (e) { errorMessage = e.toString(); } } Future<Null> addHero(String name) async { name = name.trim(); if (name.isEmpty) return; try { heroes.add(await _heroService.create(name)); } catch (e) { errorMessage = e.toString(); } } }

Angular injects a HeroService into the constructor and the component calls that service to fetch and save data.

The component does not talk directly to the Dart BrowserClient client. The component doesn’t know or care how it gets the data. It delegates to the HeroService.

This is a golden rule: always delegate data access to a supporting service class.

Although at runtime the component requests heroes immediately after creation, you don’t call the service’s get method in the component’s constructor. Instead, call it inside the ngOnInit lifecycle hook and rely on Angular to call ngOnInit when it instantiates this component.

This is a best practice. Components are easier to test and debug when their constructors are simple, and all real work (especially calling a remote server) is handled in a separate method.

The hero service getHeroes() and create() asynchronous methods return the Future values of the current hero list and the newly added hero, respectively. The hero list component getHeroes() and addHero() methods specify the actions to be taken when the asynchronous method calls succeed or fail.

For more information about Futures, consult any one of the articles on asynchronous programming in Dart, or the tutorial on Asynchronous Programming: Futures.

With a basic understanding of the component, you’re ready to look inside the HeroService.

Fetch data with http.get()

In many of the previous samples the app faked the interaction with the server by returning mock heroes in a service like this one:

import 'dart:async'; import 'package:angular2/angular2.dart'; import 'hero.dart'; import 'mock_heroes.dart'; @Injectable() class HeroService { Future<List<Hero>> getHeroes() async => mockHeroes; }

You can revise that HeroService to get the heroes from the server using the Dart BrowserClient client service:

lib/src/toh/hero_service.dart (revised)

import 'dart:async'; import 'dart:convert'; import 'package:angular2/angular2.dart'; import 'package:http/http.dart'; import 'hero.dart'; @Injectable() class HeroService { static final _headers = {'Content-Type': 'application/json'}; static const _heroesUrl = 'api/heroes'; // URL to web API final Client _http; HeroService(this._http); Future<List<Hero>> getHeroes() async { try { final response = await _http.get(_heroesUrl); final heroes = _extractData(response) .map((value) => new Hero.fromJson(value)) .toList(); return heroes; } catch (e) { throw _handleError(e); } } Future<Hero> create(String name) async { try { final response = await _http.post(_heroesUrl, headers: _headers, body: JSON.encode({'name': name})); return new Hero.fromJson(_extractData(response)); } catch (e) { throw _handleError(e); } }

Notice that the Dart BrowserClient client service is injected into the HeroService constructor.

HeroService(this._http);

Look closely at how to call _http.get:

lib/src/toh/hero_service.dart (getHeroes)

static const _heroesUrl = 'api/heroes'; // URL to web API Future<List<Hero>> getHeroes() async { try { final response = await _http.get(_heroesUrl); final heroes = _extractData(response) .map((value) => new Hero.fromJson(value)) .toList(); return heroes; } catch (e) { throw _handleError(e); } }

You pass the resource URL to get and it calls the server which returns heroes.

The server returns heroes once you’ve set up the in-memory web api described in the tutorial. Alternatively, you can temporarily target a JSON file by changing the endpoint URL:

static const _heroesUrl = 'heroes.json'; // URL to JSON file

Process the response object

Remember that the getHeroes() method used an _extractData() helper method to map the _http.get response object to heroes:

lib/src/toh/hero_service.dart (excerpt)

dynamic _extractData(Response resp) => JSON.decode(resp.body)['data'];

The response object doesn’t hold the data in a form the app can use directly. You must parse the response data into a JSON object.

Parse to JSON

The response data are in JSON string form. You must parse that string into objects, which you do by calling the JSON.decode() method from the dart:convert library.

Don’t expect the decoded JSON to be the heroes list directly. This server always wraps JSON results in an object with a data property. You have to unwrap it to get the heroes. This is conventional web API behavior, driven by security concerns.

Make no assumptions about the server API. Not all servers return an object with a data property.

Do not return the response object

The getHeroes() method could have returned the HTTP response but this wouldn’t follow best practices. The point of a data service is to hide the server interaction details from consumers. The component that calls the HeroService only wants heroes and is kept separate from getting them, the code dealing with where they come from, and the response object.

Always handle errors

An important part of dealing with I/O is anticipating errors by preparing to catch them and do something with them. One way to handle errors is to pass an error message back to the component for presentation to the user, but only if it says something that the user can understand and act upon.

This simple app conveys that idea, albeit imperfectly, in the way it handles a getHeroes error.

lib/src/toh/hero_service.dart (excerpt)

Future<List<Hero>> getHeroes() async { try { final response = await _http.get(_heroesUrl); final heroes = _extractData(response) .map((value) => new Hero.fromJson(value)) .toList(); return heroes; } catch (e) { throw _handleError(e); } } Exception _handleError(dynamic e) { print(e); // for demo purposes only return new Exception('Server error; cause: $e'); }

HeroListComponent error handling

Back in the HeroListComponent, you wrapped the call to _heroService.getHeroes() in a try clause. When an exception is caught, the errorMessage variable — which you’ve bound conditionally in the template — gets assigned to.

lib/src/toh/hero_list_component.dart (getHeroes)

Future<Null> getHeroes() async { try { heroes = await _heroService.getHeroes(); } catch (e) { errorMessage = e.toString(); } }

Want to see it fail? In the HeroService, reset the api endpoint to a bad value. Afterward, remember to restore it.

Send data to the server

So far you’ve seen how to retrieve data from a remote location using an HTTP service. Now you’ll add the ability to create new heroes and save them in the backend.

You’ll write a method for the HeroListComponent to call, a create() method, that takes just the name of a new hero. It begins like this:

Future<Hero> create(String name) async {

To implement it, you must know the server’s API for creating heroes. This sample’s data server follows typical REST guidelines. It expects a POST request at the same endpoint as GET heroes. It expects the new hero data to arrive in the body of the request, structured like a Hero entity but without the id property. The body of the request should look like this:

{ "name": "Windstorm" }

The server generates the id and returns the entire JSON representation of the new hero including its generated id. The hero arrives tucked inside a response object with its own data property.

Now that you know how the API works, implement create() as follows:

lib/src/toh/hero_service.dart (create)

Future<Hero> create(String name) async { try { final response = await _http.post(_heroesUrl, headers: _headers, body: JSON.encode({'name': name})); return new Hero.fromJson(_extractData(response)); } catch (e) { throw _handleError(e); } }

Headers

In the headers object, the Content-Type specifies that the body represents JSON.

JSON results

As with getHeroes(), use the _extractData() helper to extract the data from the response.

Back in the HeroListComponent, the addHero() method waits for the service’s asynchronous create() method to create a hero. When create() is finished, addHero() puts the new hero in the heroes list for presentation to the user.

lib/src/toh/hero_list_component.dart (addHero)

Future<Null> addHero(String name) async { name = name.trim(); if (name.isEmpty) return; try { heroes.add(await _heroService.create(name)); } catch (e) { errorMessage = e.toString(); } }

Cross-Origin Requests: Wikipedia example

You just learned how to make XMLHttpRequests using the Dart BrowserClient service. This is the most common approach to server communication, but it doesn’t work in all scenarios.

For security reasons, web browsers block XHR calls to a remote server whose origin is different from the origin of the web page. The origin is the combination of URI scheme, hostname, and port number. This is called the same-origin policy.

Modern browsers do allow XHR requests to servers from a different origin if the server supports the CORS protocol. If the server requires user credentials, enable them in the request headers.

Some servers do not support CORS but do support an older, read-only alternative called JSONP. Wikipedia is one such server.

This Stack Overflow answer covers many details of JSONP.

Search Wikipedia

Here is a simple search that shows suggestions from Wikipedia as the user types in a text box:

Wikipedia search app (v.1)

Wikipedia offers a modern CORS API and a legacy JSONP search API.

The remaining content of this section is coming soon. In the meantime, consult the example sources to see how to access Wikipedia via its JSONP API.

See the full source code in the .