This article is part 3 of the SitePoint Angular 2+ Tutorial on how to create a CRUD App with the Angular CLI.
- Part 0— The Ultimate Angular CLI Reference Guide
- Part 1— Getting our first version of the Todo application up and running
- Part 2— Creating separate components to display a list of todo's and a single todo
- Part 3— Update the Todo service to communicate with a REST API
- Part 4— Use Angular Router to resolve data
- 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:
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 newApiService
- update the
AppComponent
to handle asynchronous API calls - create an
ApiMockService
to avoid real HTTP calls when running unit tests
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'sGET /todos/:id
: get an existing todoPOST /todos
: create a new todoPUT /todos/:id
: update an existing todoDELETE /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:
- store the URL in a single place so that we only have to change it once when we need to change its value
- 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
orng 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 requestget(url, options)
: perform a GET requesthead(url, options)
: perform a HEAD requestoptions(url, options)
: perform an OPTIONS requestpatch(url, body, options)
: perform a PATCH requestpost(url, body, options)
: perform a POST requestput(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 todowe 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