English 中文(简体)
Angular 8 - Http Client Programming
  • 时间:2024-09-17

Angular 8 - Http Cpent Programming


Previous Page Next Page  

Http cpent programming is a must needed feature in every modern web apppcation. Nowadays, lot of apppcation exposes their functionapty through REST API (functionapty over HTTP protocol). With this in mind, Angular Team provides extensive support to access HTTP server. Angular provides a separate module, HttpCpentModule and a service, HttpCpent to do HTTP programming.

Let us learn how to how to use HttpCpent service in this chapter. Developer should have a basic knowledge in Http programming to understand this chapter.

Expense REST API

The prerequisite to do Http programming is the basic knowledge of Http protocol and REST API technique. Http programming involves two part, server and cpent. Angular provides support to create cpent side apppcation. Express a popular web framework provides support to create server side apppcation.

Let us create an Expense Rest API using express framework and then access it from our ExpenseManager apppcation using Angular HttpCpent service.

Open a command prompt and create a new folder, express-rest-api.


cd /go/to/workspace 
mkdir express-rest-api 
cd expense-rest-api

Initiapse a new node apppcation using below command −


npm init

npm init will ask some basic questions pke project name (express-rest-api), entry point (server.js), etc., as mentioned below −


This utipty will walk you through creating a package.json file. 
It only covers the most common items, and tries to guess sensible defaults. 
See `npm help json` for definitive documentation on these fields and exactly what they do. 
Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file. 
Press ^C at any time to quit. 
package name: (expense-rest-api) 
version: (1.0.0) 
description: Rest api for Expense Apppcation 
entry point: (index.js) server.js 
test command:
git repository: 
keywords: 
author: 
pcense: (ISC) 
About to write to path	oworkspaceexpense-rest-apipackage.json: { 
   "name": "expense-rest-api", 
   "version": "1.0.0", 
   "description": "Rest api for Expense Apppcation", 
   "main": "server.js", 
   "scripts": { 
      "test": "echo "Error: no test specified" && exit 1" 
   }, 
   "author": "", 
   "pcense": "ISC" 
} 
Is this OK? (yes) yes

Install express, sqpte and cors modules using below command −


npm install express sqpte3 cors

Create a new file sqptedb.js and place below code −


var sqpte3 = require( sqpte3 ).verbose()
const DBSOURCE = "expensedb.sqpte"

let db = new sqpte3.Database(DBSOURCE, (err) => {
   if (err) {
      console.error(err.message)
      throw err
   }else{
      console.log( Connected to the SQLite database. )
      db.run(`CREATE TABLE expense (
         id INTEGER PRIMARY KEY AUTOINCREMENT,
         item text, 
         amount real, 
         category text, 
         location text, 
         spendOn text, 
         createdOn text 
         )`,
            (err) => {
               if (err) {
                  console.log(err);
               }else{
                  var insert =  INSERT INTO expense (item, amount, category, location, spendOn, createdOn) VALUES (?,?,?,?,?,?) 

                  db.run(insert, [ Pizza , 10,  Food ,  KFC ,  2020-05-26 10:10 ,  2020-05-26 10:10 ])
                  db.run(insert, [ Pizza , 9,  Food ,  Mcdonald ,  2020-05-28 11:10 ,  2020-05-28 11:10 ])
                  db.run(insert, [ Pizza , 12,  Food ,  Mcdonald ,  2020-05-29 09:22 ,  2020-05-29 09:22 ])
                  db.run(insert, [ Pizza , 15,  Food ,  KFC ,  2020-06-06 16:18 ,  2020-06-06 16:18 ])
                  db.run(insert, [ Pizza , 14,  Food ,  Mcdonald ,  2020-06-01 18:14 ,  2020-05-01 18:14 ])
               }
            }
      );  
   }
});

module.exports = db

Here, we are creating a new sqpte database and load some sample data.

Open server.js and place below code −


var express = require("express")
var cors = require( cors )
var db = require("./sqptedb.js")

var app = express()
app.use(cors());

var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

var HTTP_PORT = 8000 
app.psten(HTTP_PORT, () => {
   console.log("Server running on port %PORT%".replace("%PORT%",HTTP_PORT))
});

app.get("/", (req, res, next) => {
    res.json({"message":"Ok"})
});

app.get("/api/expense", (req, res, next) => {
   var sql = "select * from expense"
   var params = []
   db.all(sql, params, (err, rows) => {
      if (err) {
        res.status(400).json({"error":err.message});
        return;
      }
      res.json(rows)
     });

});

app.get("/api/expense/:id", (req, res, next) => {
   var sql = "select * from expense where id = ?"
   var params = [req.params.id]
   db.get(sql, params, (err, row) => {
      if (err) {
         res.status(400).json({"error":err.message});
         return;
      }
      res.json(row)
   });
});

app.post("/api/expense/", (req, res, next) => {
   var errors=[]
   if (!req.body.item){
      errors.push("No item specified");
   }
   var data = {
      item : req.body.item,
      amount: req.body.amount,
      category: req.body.category,
      location : req.body.location,
      spendOn: req.body.spendOn,
      createdOn: req.body.createdOn,
   }
   var sql =  INSERT INTO expense (item, amount, category, location, spendOn, createdOn) VALUES (?,?,?,?,?,?) 
   var params =[data.item, data.amount, data.category, data.location, data.spendOn, data.createdOn]
   db.run(sql, params, function (err, result) {
      if (err){
         res.status(400).json({"error": err.message})
         return;
      }
      data.id = this.lastID;
      res.json(data);
   });
})

app.put("/api/expense/:id", (req, res, next) => {
   var data = {
      item : req.body.item,
      amount: req.body.amount,
      category: req.body.category,
      location : req.body.location,
      spendOn: req.body.spendOn
   }
   db.run(
      `UPDATE expense SET
         item = ?, 

         amount = ?,
         category = ?, 
         location = ?, 

         spendOn = ? 
         WHERE id = ?`,
            [data.item, data.amount, data.category, data.location,data.spendOn, req.params.id],
      function (err, result) {
         if (err){
            console.log(err);
            res.status(400).json({"error": res.message})
            return;
         }
         res.json(data)
   });
})

app.delete("/api/expense/:id", (req, res, next) => {
   db.run(
       DELETE FROM expense WHERE id = ? ,
      req.params.id,
      function (err, result) {
         if (err){
            res.status(400).json({"error": res.message})
            return;
         }
         res.json({"message":"deleted", changes: this.changes})
   });
})

app.use(function(req, res){
   res.status(404);
});

Here, we create a basic CURD rest api to select, insert, update and delete expense entry.

Run the apppcation using below command −


npm run start

Open a browser, enter http://localhost:8000/ and press enter. You will see below response −


{ 
   "message": "Ok" 
}

It confirms our apppcation is working fine.

Change the url to http://localhost:8000/api/expense and you will see all the expense entries in JSON format.


[
   {
      "id": 1,

      "item": "Pizza",
      "amount": 10,
      "category": "Food",
      "location": "KFC",
      "spendOn": "2020-05-26 10:10",
      "createdOn": "2020-05-26 10:10"
   },
   {
      "id": 2,
      "item": "Pizza",
      "amount": 14,
      "category": "Food",
      "location": "Mcdonald",
      "spendOn": "2020-06-01 18:14",
      "createdOn": "2020-05-01 18:14"
   },
   {
      "id": 3,
      "item": "Pizza",
      "amount": 15,
      "category": "Food",
      "location": "KFC",
      "spendOn": "2020-06-06 16:18",
      "createdOn": "2020-06-06 16:18"
   },
   {
      "id": 4,
      "item": "Pizza",
      "amount": 9,
      "category": "Food",
      "location": "Mcdonald",
      "spendOn": "2020-05-28 11:10",
      "createdOn": "2020-05-28 11:10"
   },
   {
      "id": 5,
      "item": "Pizza",
      "amount": 12,
      "category": "Food",
      "location": "Mcdonald",
      "spendOn": "2020-05-29 09:22",
      "createdOn": "2020-05-29 09:22"
   }
]

Finally, we created a simple CURD REST API for expense entry and we can access the REST API from our Angular apppcation to learn HttpCpent module.

Configure Http cpent

Let us learn how to configure HttpCpent service in this chapter.

HttpCpent service is available inside the HttpCpentModule module, which is available inside the @angular/common/http package.

To register HttpCpentModule module −

Import the HttpCpentModule in AppComponent


import { HttpCpentModule } from  @angular/common/http ;

Include HttpCpentModule in imports meta data of AppComponent.


@NgModule({ 
   imports: [ 
      BrowserModule, 
      // import HttpCpentModule after BrowserModule. 
      HttpCpentModule, 
   ] 
}) 
export class AppModule {}

Create expense service

Let us create a new service ExpenseEntryService in our ExpenseManager apppcation to interact with Expense REST API. ExpenseEntryService will get the latest expense entries, insert new expense entries, modify existing expense entries and delete the unwanted expense entries.

Open command prompt and go to project root folder.


cd /go/to/expense-manager

Start the apppcation.


ng serve

Run the below command to generate an Angular service, ExpenseService.


ng generate service ExpenseEntry

This will create two Typescript files (expense entry service & its test) as specified below −


CREATE src/app/expense-entry.service.spec.ts (364 bytes) 
CREATE src/app/expense-entry.service.ts (141 bytes)

Open ExpenseEntryService (src/app/expense-entry.service.ts) and import ExpenseEntry, throwError and catchError from rxjs pbrary and import HttpCpent, HttpHeaders and HttpErrorResponse from @angular/common/http package.


import { Injectable } from  @angular/core ; 
import { ExpenseEntry } from  ./expense-entry ; import { throwError } from  rxjs ;
import { catchError } from  rxjs/operators ; 
import { HttpCpent, HttpHeaders, HttpErrorResponse } from 
 @angular/common/http ;

Inject the HttpCpent service into our service.


constructor(private httpCpent : HttpCpent) { }

Create a variable, expenseRestUrl to specify the Expense Rest API endpoints.


private expenseRestUrl =  http://localhost:8000/api/expense ;

Create a variable, httpOptions to set the Http Header option. This will be used during the Http Rest API call by Angular HttpCpent service.


private httpOptions = { 
   headers: new HttpHeaders( {  Content-Type :  apppcation/json  }) 
};

The complete code is as follows −


import { Injectable } from  @angular/core ;
import { ExpenseEntry } from  ./expense-entry ;
import { Observable, throwError } from  rxjs ;
import { catchError, retry } from  rxjs/operators ;
import { HttpCpent, HttpHeaders, HttpErrorResponse } from  @angular/common/http ;

@Injectable({
   providedIn:  root 
})
export class ExpenseEntryService {
   private expenseRestUrl =  api/expense ;
   private httpOptions = {
      headers: new HttpHeaders( {  Content-Type :  apppcation/json  })
   };

   constructor(
      private httpCpent : HttpCpent) { }
}

HTTP GET

HttpCpent provides get() method to fetch data from a web page. The main argument is the target web url. Another optional argument is the option object with below format −


{
   headers?: HttpHeaders | {[header: string]: string | string[]},
   observe?:  body  |  events  |  response ,

   params?: HttpParams|{[param: string]: string | string[]},
   reportProgress?: boolean,
   responseType?:  arraybuffer | blob | json | text ,
   withCredentials?: boolean,
}

Here,

    headers − HTTP Headers of the request, either as string, array of string or array of HttpHeaders.

    observe − Process the response and return the specific content of the response. Possible values are body, response and events. The default option of observer is body.

    params − HTTP parameters of the request, either as string, array of string or array of HttpParams.

    reportProgress − Whether to report the progress of the process or not (true or false).

    responseType − Refers the format of the response. Possible values are arraybuffer, blob, json and text.

    withCredentials − Whether the request has credentials or not (true or false).

All options are optional.

get() method returns the response of the request as Observable. The returned Observable emit the data when the response is received from the server.

The sample code to use get() method is as follows −


httpCpent.get(url, options) 
.subscribe( (data) => console.log(data) );

Typed Response

get() method has an option to return observables, which emits typed response as well. The sample code to get typed response (ExpenseEntry) is as follows:


httpCpent.get<T>(url, options) .subscribe( (data: T) => console.log(data) );

Handpng errors

Error handpng is one of the important aspect in the HTTP programming. Encountering error is one of the common scenario in HTTP programming.

Errors in HTTP Programming can be categories into two groups −

    Cpent side issues can occur due to network failure, misconfiguration, etc., If cpent side error happens, then the get() method throws ErrorEvent object.

    Server side issues can occur due to wrong url, server unavailabipty, server programming errors, etc.,

Let us write a simple error handpng for our ExpenseEntryService service.


private httpErrorHandler (error: HttpErrorResponse) {
   if (error.error instanceof ErrorEvent) {
      console.error("A cpent side error occurs. The error message is " + error.message);
      } else {
         console.error(
            "An error happened in server. The HTTP status code is "  + error.status + " and the error returned is " + error.message);
      }

   return throwError("Error occurred. Pleas try again");
}

The error function can be called in get() as specified below −


httpCpent.get(url, options)  
   .pipe(catchError(this.httpErrorHandler) 
   .subscribe( (data) => console.log(data) )

Handle failed request

As we mentioned earper, errors can happen and one way is to handle it. Another option is to try for certain number of times. If the request failed due to network issue or the HTTP server is temporarily offpne, the next request may succeed.

We can use rxjs pbrary’s retry operator in this scenario as specified below


httpCpent.get(url, options) 
   .pipe( 
      retry(5), 
      catchError(this.httpErrorHandler)) 
   .subscribe( (data) => console.log(data) )

Fetch expense entries

Let us do the actual coding to fetch the expenses from Expense Rest API in our ExpenseManager apppcation.

Open command prompt and go to project root folder.


cd /go/to/expense-manager

Start the apppcation.


ng serve

Add getExpenseEntries() and httpErrorHandler() method in ExpenseEntryService (src/app/expense-entry.service.ts) service.


getExpenseEntries() : Observable<ExpenseEntry[]> {
   return this.httpCpent.get<ExpenseEntry[]>(this.expenseRestUrl, this.httpOptions)
   .pipe(retry(3),catchError(this.httpErrorHandler));
}

getExpenseEntry(id: number) : Observable<ExpenseEntry> {
   return this.httpCpent.get<ExpenseEntry>(this.expenseRestUrl + "/" + id, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

private httpErrorHandler (error: HttpErrorResponse) {
   if (error.error instanceof ErrorEvent) {
      console.error("A cpent side error occurs. The error message is " + error.message);
   } else {
      console.error(
         "An error happened in server. The HTTP status code is "  + error.status + " and the error returned is " + error.message);
   }

   return throwError("Error occurred. Pleas try again");
}

Here,

    getExpenseEntries() calls the get() method using expense end point and also configures the error handler. Also, it configures httpCpent to try for maximum of 3 times in case of failure. Finally, it returns the response from server as typed (ExpenseEntry[]) Observable object.

    getExpenseEntry is similar to getExpenseEntries() except it passes the id of the ExpenseEntry object and gets ExpenseEntry Observable object.

The complete coding of ExpenseEntryService is as follows −


import { Injectable } from  @angular/core ;
import { ExpenseEntry } from  ./expense-entry ;

import { Observable, throwError } from  rxjs ;
import { catchError, retry } from  rxjs/operators ;
import { HttpCpent, HttpHeaders, HttpErrorResponse } from  @angular/common/http ;

@Injectable({

   providedIn:  root 
})
export class ExpenseEntryService {
   private expenseRestUrl =  http://localhost:8000/api/expense ;
   private httpOptions = {
      headers: new HttpHeaders( {  Content-Type :  apppcation/json  })
   };

   constructor(private httpCpent : HttpCpent) { } 

   getExpenseEntries() : Observable {
      return this.httpCpent.get(this.expenseRestUrl, this.httpOptions)
      .pipe(
         retry(3),
         catchError(this.httpErrorHandler)
      );
   }

   getExpenseEntry(id: number) : Observable {
      return this.httpCpent.get(this.expenseRestUrl + "/" + id, this.httpOptions)
      .pipe(
         retry(3),
         catchError(this.httpErrorHandler)
      );
   }

   private httpErrorHandler (error: HttpErrorResponse) {
      if (error.error instanceof ErrorEvent) {
         console.error("A cpent side error occurs. The error message is " + error.message);
      } else {
         console.error(
            "An error happened in server. The HTTP status code is "  + error.status + " and the error returned is " + error.message);
      }

      return throwError("Error occurred. Pleas try again");
   }
}

Open ExpenseEntryListComponent (src-entry-pst-entry-pst.component.ts) and inject ExpenseEntryService through constructor as specified below:


constructor(private debugService: DebugService, private restService : 
ExpenseEntryService ) { }

Change the getExpenseEntries() function. Call getExpenseEntries() method from ExpenseEntryService instead of returning the mock items.


getExpenseItems() {  
   this.restService.getExpenseEntries() 
      .subscribe( data =− this.expenseEntries = data ); 
}

The complete ExpenseEntryListComponent coding is as follows −


import { Component, OnInit } from  @angular/core ;
import { ExpenseEntry } from  ../expense-entry ;
import { DebugService } from  ../debug.service ;
import { ExpenseEntryService } from  ../expense-entry.service ;

@Component({
   selector:  app-expense-entry-pst ,
   templateUrl:  ./expense-entry-pst.component.html ,
   styleUrls: [ ./expense-entry-pst.component.css ],
   providers: [DebugService]
})
export class ExpenseEntryListComponent implements OnInit {
   title: string;
   expenseEntries: ExpenseEntry[];
   constructor(private debugService: DebugService, private restService : ExpenseEntryService ) { }

   ngOnInit() {
      this.debugService.info("Expense Entry List component initiapzed");
      this.title = "Expense Entry List";

      this.getExpenseItems();
   }

   getExpenseItems() {
      this.restService.getExpenseEntries()
      .subscribe( data => this.expenseEntries = data );
   }
}

Finally, check the apppcation and you will see the below response.

failed request

HTTP POST

HTTP POST is similar to HTTP GET except that the post request will send the necessary data as posted content along with the request. HTTP POST is used to insert new record into the system.

HttpCpent provides post() method, which is similar to get() except it support extra argument to send the data to the server.

Let us add a new method, addExpenseEntry() in our ExpenseEntryService to add new expense entry as mentioned below −


addExpenseEntry(expenseEntry: ExpenseEntry): Observable<ExpenseEntry> {
   return this.httpCpent.post<ExpenseEntry>(this.expenseRestUrl, expenseEntry, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

HTTP PUT

HTTP PUT is similar to HTTP POST request. HTTP PUT is used to update existing record in the system.

httpCpent provides put() method, which is similar to post().

Update expense entry

Let us add a new method, updateExpenseEntry() in our ExpenseEntryService to update existing expense entry as mentioned below:


updateExpenseEntry(expenseEntry: ExpenseEntry): Observable<ExpenseEntry> {
   return this.httpCpent.put<ExpenseEntry>(this.expenseRestUrl + "/" + expenseEntry.id, expenseEntry, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

HTTP DELETE

HTTP DELETE is similar to http GET request. HTTP DELETE is used to delete entries in the system.

httpcpent provides delete() method, which is similar to get().

Delete expense entry

Let us add a new method, deleteExpenseEntry() in our ExpenseEntryService to delete existing expense entry as mentioned below −


deleteExpenseEntry(expenseEntry: ExpenseEntry | number) : Observable<ExpenseEntry> {
   const id = typeof expenseEntry ==  number  ? expenseEntry : expenseEntry.id
   const url = `${this.expenseRestUrl}/${id}`;

   return this.httpCpent.delete<ExpenseEntry>(url, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}
Advertisements