Monday, June 5, 2017

Angular and RxJS: Create an API Service to Talk to a REST Backend

This article is part 3 of the SitePoint Angular 2+ Tutorial on how to create a CRUD App with the Angular CLI.


  1. Part 0— The Ultimate Angular CLI Reference Guide
  2. Part 1— Getting our first version of the Todo application up and running
  3. Part 2— Creating separate components to display a list of todo's and a single todo
  4. Part 3— Update the Todo service to communicate with a REST API
  5. Part 4— Use Angular Router to resolve data
  6. Part 5— Add authentication to protect private content

In part one we learned how to get our Todo application up and running and deploy it to GitHub pages. This worked just fine but, unfortunately, the whole app was crammed into a single component.

In part two we examined a more modular component architecture and learned how to break this single component into a structured tree of smaller components that are easier to understand, re-use and maintain.

In this part, we will update our application to communicate with a REST API back-end.

You don't need to have followed part one or two of this tutorial, for three to make sense. You can simply grab a copy of our repo, checkout the code from part two, and use that as a starting point. This is explained in more detail below.

A Quick Recap

Here is what our application architecture looked like at the end of part 2:

Application Architecture

Currently the TodoDataService stores all data in memory. In this third article, we will update our application to communicate with a REST API back-end instead.

We will:

  • create a mock REST API back-end
  • store the API URL as an environment variable
  • create an ApiService to communicate with the REST API
  • update the TodoDataService to use the new ApiService
  • update the AppComponent to handle asynchronous API calls
  • create an ApiMockService to avoid real HTTP calls when running unit tests

Application Architecture

By the end of this article, you will understand:

  • how you can use environment variables to store application settings
  • how you can use the Angular HTTP client to perform HTTP requests
  • how you can deal with Observables that are returned by the Angular HTTP client
  • how you can mock HTTP calls to avoid making real HTTP request when running unit tests

So, let's get started!

Up and Running

Make sure you have the latest version of the Angular CLI installed. If you don't, you can install this with the following command:

npm install -g @angular/cli@latest

If you need to remove a previous version of the Angular CLI, you can:

npm uninstall -g @angular/cli angular-cli
npm cache clean
npm install -g @angular/cli@latest

After that, you'll need a copy of the code from part two. This is available at http://ift.tt/2mpeXuK. Each article in this series has a corresponding tag in the repository so you can switch back and forth between the different states of the application.

The code that we ended with in part two and that we start with in this article is tagged as part-2. The code that we end this article with is tagged as part-3.

You can think of tags like an alias to a specific commit id. You can switch between them using git checkout. You can read more on that here.

So, to get up and running (the latest version of the Angular CLI installed) we would do:

git clone git@github.com:sitepoint-editors/angular-todo-app.git
cd angular-todo-app
git checkout part-2
npm install
ng serve

Then visit http://localhost:4200/. If all is well, you should see the working Todo app.

Setting up a REST API back-end

Let's use json-server to quickly set up a mock back-end.

From the root of the application, run:

npm install json-server --save

Next, in the root directory of our application, create a file called db.json with the following contents:

{
  "todos": [
    {
      "id": 1,
      "title": "Read SitePoint article",
      "complete": false
    },
    {
      "id": 2,
      "title": "Clean inbox",
      "complete": false
    },
    {
      "id": 3,
      "title": "Make restaurant reservation",
      "complete": false
    }
  ]
} 

Finally, add a script to package.json to start our back-end:

"scripts": {
  ...
  "json-server": "json-server --watch db.json"
}

We can now launch our REST API using:

npm run json-server

which should display:

  \{^_^}/ hi!

  Loading db.json
  Done

  Resources
  http://localhost:3000/todos

  Home
  http://localhost:3000

That's it! We now have a REST API listening on port 3000.

To verify that your back-end is running as expected, you can navigate your browser to http://localhost:3000.

The following endpoints are supported:

  • GET /todos: get all existing todo's
  • GET /todos/:id: get an existing todo
  • POST /todos: create a new todo
  • PUT /todos/:id: update an existing todo
  • DELETE /todos/:id: delete an existing todo

so if you navigate your browser to http://localhost:3000/todos, you should see a JSON response with all todo's from db.json.

To learn more about json-server, make sure to check out mock REST API's using json-server.

Storing the API URL

Now that we have our back-end in place, we must store its URL in our Angular application.

Ideally, we should be able to:

  1. store the URL in a single place so that we only have to change it once when we need to change its value
  2. make our application connect to a development API during development and connect to a production API in production

Luckily, Angular CLI supports environments. By default, there are two environments: development and production, both with a corresponding environment file: src/environments/environment.ts and 'src/environments/environment.prod.ts.

Let's add our API URL to both files:

// src/environments/environment.ts
// used when we run `ng serve` or `ng build`
export const environment = {
  production: false,

  // URL of development API
  apiUrl: 'http://localhost:3000'
};

// src/environments/environment.prod.ts
// used when we run `ng serve --environment prod` or `ng build --environment prod`
export const environment = {
  production: true,

  // URL of production API
  apiUrl: 'http://localhost:3000'
};

This will later allow us to get the API URL from our environment in our Angular application by doing:

import { environment } from 'environments/environment';

// we can now access environment.apiUrl
const API_URL = environment.apiUrl;

When we run ng serve or ng build, Angular CLI uses the value specified in the development environment (src/environments/environment.ts).

But when we run ng serve --environment prod or ng build --environment prod, Angular CLI uses the value specified in src/environments/environment.prod.ts.

This is exactly what we need to use a different API URL for development and production, without having to change our code.

The application in this article series is not hosted in production, so we specify the same API URL in our development and production environment. This allows us to run ng serve --environment prod or ng build --environment prod locally to see if everything works as expected.

You can find the mapping between dev and prod and their corresponding environment files in .angular-cli.json:

"environments": {
  "dev": "environments/environment.ts",
  "prod": "environments/environment.prod.ts"
} 

You can also create additional environments such as staging by adding a key:

"environments": {
  "dev": "environments/environment.ts",
  "staging": "environments/environment.staging.ts",
  "prod": "environments/environment.prod.ts"
}

and creating the corresponding environment file.

To learn more about Angular CLI environments, make sure to check out the The Ultimate Angular CLI Reference Guide.

Now that we have our API URL stored in our environment, we can create an Angular service to communicate with the REST API.

Creating the Service to Communicate with the REST API

Let's use Angular CLI to create an ApiService to communicate with our REST API:

ng generate service Api --module app.module.ts

which gives the following output:

installing service
  create src/app/api.service.spec.ts
  create src/app/api.service.ts
  update src/app/app.module.ts

The --module app.module.ts option tells Angular CLI to not only create the service but to also register it as a provider in the Angular module defined in app.module.ts.

Let's open src/app/api.service.ts:

import { Injectable } from '@angular/core';

@Injectable()
export class ApiService {

  constructor() { }

} 

and inject our environment and Angular's built-in HTTP service:

import { Injectable } from '@angular/core';
import { environment } from 'environments/environment';
import { Http } from '@angular/http';

const API_URL = environment.apiUrl;

@Injectable()
export class ApiService {

  constructor(
    private http: Http
  ) {
  }

}

Before we implement the methods we need, let's have a look at Angular's HTTP service.

If you're unfamiliar with the syntax, why not buy our Premium course, Introducing TypeScript.

The Angular HTTP Service

The Angular HTTP service is available as an injectable class from @angular/http.

It is built on top of XHR/JSONP and provides us with an HTTP client that we can use to make HTTP requests from within our Angular application.

The following methods are available to perform HTTP requests:

  • delete(url, options): perform a DELETE request
  • get(url, options): perform a GET request
  • head(url, options): perform a HEAD request
  • options(url, options): perform an OPTIONS request
  • patch(url, body, options): perform a PATCH request
  • post(url, body, options): perform a POST request
  • put(url, body, options): perform a PUT request

Each of these methods returns an RxJS Observable.

In contrast to the AngularJS 1.x HTTP service methods, which returned promises, the Angular HTTP service methods return Observables.

Don't worry if you are not yet familiar with RxJS Observables. We only need the basics to get our application up and running. You can gradually learn more about the available operators when your application requires them and the ReactiveX website offers fantastic documentation.

If you want to learn more about Observables, it may also be worth checking out SitePoint's Introduction to Functional Reactive Programming with RxJS.

Implementing the ApiService Methods

If we think back of the endpoints our REST API back-end exposes:

  • GET /todos: get all existing todo's

  • GET /todos/:id: get an existing todo

  • POST /todos: create a new todo

  • PUT /todos/:id: update an existing todo

  • DELETE /todos/:id: delete an existing todo

    we can already create a rough outline of methods we need and their corresponding Angular HTTP methods:

import { Injectable } from '@angular/core';
import { environment } from 'environments/environment';

import { Http, Response } from '@angular/http';
import { Todo } from './todo';
import { Observable } from 'rxjs/Observable';

const API_URL = environment.apiUrl;

@Injectable()
export class ApiService {

  constructor(
    private http: Http
  ) {
  }

  // API: GET /todos
  public getAllTodos() {
    // will use this.http.get()
  }

  // API: POST /todos
  public createTodo(todo: Todo) {
    // will use this.http.post()
  }

  // API: GET /todos/:id
  public getTodoById(todoId: number) {
    // will use this.http.get()
  }

  // API: PUT /todos/:id
  public updateTodo(todo: Todo) {
    // will use this.http.put()
  }

  // DELETE /todos/:id
  public deleteTodoById(todoId: number) {
    // will use this.http.delete()
  }
}

Let's have a closer look at each of the methods.

Continue reading %Angular and RxJS: Create an API Service to Talk to a REST Backend%


by Jurgen Van de Moere via SitePoint

No comments:

Post a Comment