✒️
Start your developer blog today!
✉️
Questions?
Email me

How to Build an Angular 9 App That Connects with a REST API

Apr 26, 2020 8 min read

In this brief guide, I’ll be demonstrating how Angular 9 applications can connect to a REST API.

We will do the following:

  • Create an Angular 9 project
  • Set up a fake API server that you can replace with a real one
  • Make GET requests from our project to the server and handle errors
  • Display all entries in the response onto the browser using Angular Material

Throughout this article, we’ll be using three terminals:

  • Terminal 1 will be running our Angular project.
  • Terminal 2 will be running a server running our fake API.
  • Terminal 3 will be used to modify our Angular project.

1. Install Angular CLI

Let’s start by opening up Terminal 1 and installing the Angular 9 CLI. This gives us access to the ng command, which is how we’ll be interacting with our Angular projects. We’ll use npm to install @angular/cli.

# Terminal 1
npm install -g @angular/cli

At the time of writing this guide, I am running version 9.1.2 for Angular CLI. You can check your version number by running ng version and looking at Angular CLI: x.x.x.

2. Create an Angular 9 Project

Navigate to the folder in which you’d like to create your Angular project, and create your project using ng new.

We’ll create our project in our home directory ~ and then navigate into the Angular project.

# Terminal 1
cd ~
ng new angular-demo

Next, we’re going to start up our local development server using ng serve, which will start our server for us and watch for file changes.

# Terminal 1
cd angular-demo
ng serve --open

We want to see this output.

** Angular Live Development Server is listening on localhost:4200,
open your browser on http://localhost:4200/ **
Compiled successfully.

This means our Angular application is available to view at http://localhost:4200, which should open up automatically due to the --open flag.

Making Changes

Whenever we make a change to the code in our ~/angular-demo folder, the local server will send a web socket message to the client browser notifying a change, forcing a browser refresh.

So when we save a change, our application will automatically recompile.

If we have any errors in our code, Terminal 1 will show all these errors.

If not, we’ll see the Compiled successfully message in the last line of the output.

Great! Our Terminal 1 is now set up to serve our Angular application.

3. Set Up a REST API

In this section, we’ll be using json-server and Faker.js to mock a real REST API. You can replace this with an external API service if you have one available.

Since a REST API generally functions as a separate entity, we’ll create a new folder next to angular-demo called angular-demo-api, which will hold our fake API.

Let’s open up Terminal 2, create ~/angular-demo-api, and install json-server and faker.

We will need to run npm init inside this folder, which will prompt us for a lot of information. Just hit Enter to use all the default options for the package.json setup. It’s not too important what those values are.

# Terminal 2
mkdir ~/angular-demo-api
cd angular-demo-api
npm init
npm install --save json-server faker

Inside ~/angular-demo-api, let’s create a generate.js file that will create 100 fake entries in our so-called “database”, which will be database.json.

// generate.js
let faker = require('faker');
let database = { products: [] };
for (let i = 1; i <= 100; i++) {
  database.products.push({
    id: i,
    name: faker.commerce.productName(),
    description: faker.lorem.sentences(),
    price: faker.commerce.price(),
    quantity: faker.random.number()
  });
}
console.log(JSON.stringify(database));

We now want to generate the fake entries using generator.js and store it into database.json.

We will add generator and server scripts inside package.json.

// package.json
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "generator": "node generator.js > database.json",
  "server": "json-server --watch database.json"
}

Let’s generate the fake data and then run the REST API server.

# Terminal 2
npm run generator
npm run server

Our REST API server will be available from http://localhost:3000.

These are the endpoints offered by our fake API (we will only be demonstrating GET /products).

  • GET /products for getting the products
  • GET /products/<id> for getting a single product by id
  • POST /products for creating a new product
  • PUT /products/<id> for updating a product by id
  • PATCH /products/<id> for partially updating a product by id
  • DELETE /products/<id> for deleting a product by id

Perfect. Our Terminal 2 is now set up to handle our API requests.

4. Set Up Angular Service to Handle REST API

Firstly, our service will use HttpClient to make requests to our REST API.

Inside src/app/app.module.ts, let’s import HttpClientModule and add it to our imports array.

// src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Secondly, our Angular service will handle any communication with our REST API server and appropriately set up the HttpClient requests.

Let’s open up Terminal 3 to do this.

# Terminal 3
ng generate service data

In a text editor, let’s open the newly created src/app/data.service.ts and call the get() method from HttpClient to make a GET request to our fake API server.

// src/app/data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private API_SERVER = "http://localhost:3000/products";
  constructor(private http: HttpClient) { }
  public sendGetRequest() {
    return this.http.get(this.API_SERVER);
  }
}

We’ll test out this functionality in our app component for simplicity.

When creating an API tool, be sure to create new components with ng g c component_name to make use of Angular’s modularity.

Inside app.component.ts, we’ll call sendGetRequest() upon initialization of this app component.

// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'angular-demo';
  products = [];
  constructor(private dataService: DataService) { }
  ngOnInit() {
    this.dataService.sendGetRequest().subscribe((data: any[])=>{
      console.log(data);
      this.products = data;
    });
  }
}

We can actually ensure that our Angular application is successfully communicating to our API server by heading over to http://localhost:4200 in our browser.

Let’s open up our Developer Console by pressing Ctrl+Shift+I on Windows or Ctrl+Option+I on Mac.

Since we logged the response into our console, we should see an array of 100 objects printed to our console.

(100) [{...}, {...}, {...}, {...}, ...]

Now all we have to do is display this onto our browser.

5. Set Up a UI

We’re going to use Angular Material to set up our user interface. Let’s add that right now.

# Terminal 3
ng add @angular/material

We’ll have to import the modules in app.module.ts just as we did with HttpClient.

// src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-brower/animations';

import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    BrowserAnimationsModule,
    MatToolbarModule,
    MatIconModule,
    MatButtonModule,
    MatCardModule,
    MatProgressSpinnerModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We will now populate our app.component.html using these modules. This file will contain all the default HTML for all Angular projects, so we’ll first clear the entire file and replace it with this.

In this section, products refers to products = []; inside app.component.ts. We are iterating through products using *ngFor="let p of products", which will create a new instance of mat-card per product p.

This won’t be the prettiest application you’ll ever see, but it’ll be a functional starting point.

Let’s open up src/app/app.component.html.

<div style="padding: 5px;">
    <mat-spinner *ngIf="products.length === 0"></mat-spinner>
    <mat-card *ngFor="let p of products" style="margin-top:5px;">
        <mat-card-header>
            <mat-card-title>{{p.name}}</mat-card-title>
        </mat-card-header>
        <mat-card-content>
            <p>{{p.description}}</p>
        </mat-card-content>
        <mat-card-actions>
            <button mat-button>View Product</button>
        </mat-card-actions>
    </mat-card>
</div>

There you have it! Once your application recompiles, you should be able to view all the data from our REST API on your browser.

6. Improving Our Angular Service

In this section, we’ll quickly improve data.service.ts to include client-side and server-side error handling.

JavaScript errors will return ErrorEvent objects while server and database errors will return HTTP Error Responses.

We can create a method errAlert() to respond to errors, catch any errors from our GET request using catchError(this.errAlert), and retry sending failed HTTP requests three times using retry(3).

// src/app/data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';

import { throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private API_SERVER = "http://localhost:3000/products";
  constructor(private http: HttpClient) { }
  errAlert(error: HttpErrorResponse) {
    let msg = 'Error unknown';
    if (error.error instanceof ErrorEvent)
      msg = `Error: ${error.error.message}`;
    else
      msg = `Error: ${error.message}`;
    window.alert(msg);
    return throwError(msg);
  }
  public sendGetRequest(){
    return this.http.get(this.API_SERVER).pipe(retry(3),catchError(this.errAlert));
  }
}

Conclusion

This was a a quick demonstration of how we can communicate with our REST API server from an Angular 9 application.

While this serves as a great template, there are many more standards that come with developing Angular applications.

Be sure to make use of your components instead of stuffing all your code into the root component, handle HttpClient Observables properly using takeUntil(), support sending query or URL parameters in our requests to the API server using HttpParams, and handle all basic CRUD operations (GET, POST, PUT, DELETE) inside our Angular service.

I hope this tutorial provided some basic building blocks for your Angular applications.


More JavaScript Articles