Angular provides the HttpClient
in @angular/common/http
for front-end applications communicate with backend services. In the tutorial, we show how to build an Angular application that uses the HttpClient
to make get/post/put/delete
requests with Observable
apis to SpringBoot RestAPIs.
Related posts:
– Angular 12 Service – with Observable Data for Asynchronous Operation
– Angular 12 Routing/Navigation – with Angular Router Service
– Angular 12 Template Driven Form – NgModel for Two-Way Data Binding
Contents
Technologies
- Java 1.8
- Maven 3.3.9
- Spring Tool Suite – Version 3.9.4.RELEASE
- Spring Boot: 2.0.3.RELEASE
- Angular 12
- RxJS 6
- Bootstrap 4
- Visual Studio Code – version 1.24.0
Overview
Goals
We create 2 projects:
– Angular Client Project:
– SpringBoot Server Project:
Overview
UserCase
– Retrieve all customers from SpringBoot server:
– Update a customer -> Change the firstname
of first customer: ‘Joe’ to ‘Robert’.
-> result:
– Delete ‘Peter’ customer:
– Add a new customer:
-> result:
– Check final customer’s list:
SpringBoot RestAPIs
SpringBoot Services exposes 5 RestAPIs as below:
@GetMapping(value="/api/customers") public List
getAll() @GetMapping(value="/api/customers/{id}") public Customer getCustomer(@PathVariable Long id)
@PostMapping(value="/api/customers") public Customer postCustomer(@RequestBody Customer customer)
@DeleteMapping(value="/api/customers/{id}") public void deleteCustomer(@PathVariable Long id)
@PutMapping(value="/api/customers") public void putCustomer(@RequestBody Customer customer)
– Configure cross-origin
for Angular-Client which running at port: 4200
.
@CrossOrigin(origins = "http://localhost:4200")
@RestController
public class RestAPIs{...}
Angular 12 HttpClient
Use Angular HttpClient APIs to do Get/Post/Put/Delete
requests to SpringBoot servers:
// 1. GET All Customers from remote SpringBoot API@GetMapping(value="/api/customers")
getCustomers (): Observable{ return this.http.get (this.customersUrl) } // 2. GET a Customer from remote SpringBoot API @GetMapping(value="/api/customers/{id}")
getCustomer(id: number): Observable{ const url = `${this.customersUrl}/${id}`; return this.http.get (url); } // 3. POST a Customer to remote SpringBoot API @PostMapping(value="/api/customers")
addCustomer (customer: Customer): Observable{ return this.http.post (this.customersUrl, customer, httpOptions); } // 4.DELETE a Customer from remote SpringBoot API @DeleteMapping(value="/api/customers/{id}")
deleteCustomer (customer: Customer | number): Observable{ const id = typeof customer === 'number' ? customer : customer.id; const url = `${this.customersUrl}/${id}`; return this.http.delete (url, httpOptions); } // 5. PUT a Customer to remote SpringBoot API @PutMapping(value="/api/customers")
updateCustomer (customer: Customer): Observable{ return this.http.put(this.customersUrl, customer, httpOptions); }
Practice
SpringBoot RestAPIs
We create SpringBoot project with below dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Data Model
- Create Customer data model:
package com.javasampleapproach.restapi.model;
public class Customer {
private Long id;
private String firstname;
private String lastname;
private int age;
public Customer(){
}
public Customer(String firstname, String lastname, int age){
this.firstname = firstname;
this.lastname = lastname;
this.age = age;
}
public Customer(Long id, String firstname, String lastname, int age){
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.age = age;
}
// id
public void setId(Long id){
this.id = id;
}
public Long getId(){
return this.id;
}
// firstname
public void setFirstname(String firstname){
this.firstname = firstname;
}
public String getFirstname(){
return this.firstname;
}
// lastname
public void setLastname(String lastname){
this.lastname = lastname;
}
public String getLastname(){
return this.lastname;
}
// age
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
}
Rest APIs
Implement Rest APIs for GET/POST/PUT/DELETE
customers:
package com.javasampleapproach.restapi.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.javasampleapproach.restapi.model.Customer;
@CrossOrigin(origins = "http://localhost:4200")
@RestController
public class RestAPIs {
private Map customers = new HashMap(){
private static final long serialVersionUID = 1L;
{
put(1L, new Customer(1L, "Joe", "Thomas", 36));
put(2L, new Customer(2L, "Peter", "Smith", 18));
put(3L, new Customer(3L, "Lauren", "Taylor", 31));
put(4L, new Customer(4L, "Mary", "Taylor", 24));
put(5L, new Customer(5L, "David", "Moore", 25));
put(6L, new Customer(6L, "Holly", "Davies", 27));
put(7L, new Customer(7L, "Michael", "Brown", 45));
}
};
@GetMapping(value="/api/customers")
public List getAll(){
List results = customers.entrySet().stream()
.map(entry ->entry.getValue())
.collect(Collectors.toList());
return results;
}
@GetMapping(value="/api/customers/{id}")
public Customer getCustomer(@PathVariable Long id){
return customers.get(id);
}
@PostMapping(value="/api/customers")
public Customer postCustomer(@RequestBody Customer customer){
Entry maxByKey = customers.entrySet()
.stream()
.reduce((curr, nxt) -> curr.getKey() > nxt.getKey() ? curr : nxt)
.get();
Long nextId = (long) (maxByKey.getKey() + 1);
customer.setId(nextId);
customers.put(nextId, customer);
return customer;
}
@PutMapping(value="/api/customers")
public void putCustomer(@RequestBody Customer customer){
customers.replace(customer.getId(), customer);
}
@DeleteMapping(value="/api/customers/{id}")
public void deleteCustomer(@PathVariable Long id){
customers.remove(id);
}
}
Angular 12 Client
Setup Angular Project
- Create Angular project:
ng new angular6-httpclient
- Generate:
- Customer Class
- Customer Service
- Customer Components
- App Routing Module
-> Details:
ng generate class Customer
ng generate service Customer
ng generate component Customer
ng generate component CustomerDetails
ng generate component AddCustomer
ng generate module AppRouting
– Install Bootstrap 4:
npm install bootstrap jquery --save
-> Configure installed Bootstrap & JQuery in angular.json
file:
...
"styles": [
"src/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js"
]
...
Data Model
Implement Customer model customer.ts
:
export class Customer {
id: number;
firstname: string;
lastname: string;
age: number;
}
Configure AppModule
In the developed application, we use:
- Angular
Forms
-> for building form HttpClient
-> for httpGet/Post/Put/Delete
requestsAppRouting
-> for app routing
-> Modify AppModule app.module.ts
:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing/app-routing.module';
import { AppComponent } from './app.component';
import { CustomerComponent } from './customer/customer.component';
import { CustomerDetailsComponent } from './customer-details/customer-details.component';
import { AddCustomerComponent } from './add-customer/add-customer.component';
@NgModule({
declarations: [
AppComponent,
CustomerComponent,
CustomerDetailsComponent,
AddCustomerComponent
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
HttpClient DataService
Implement CustomerService customer.service.ts
with HttpClient for Get/Post/Put/Delete
:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Customer } from './customer';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
@Injectable({
providedIn: 'root'
})
export class CustomerService {
private customersUrl = 'http://localhost:8080/api/customers'; // URL to web api
constructor(
private http: HttpClient
) { }
getCustomers (): Observable {
return this.http.get(this.customersUrl)
}
getCustomer(id: number): Observable {
const url = `${this.customersUrl}/${id}`;
return this.http.get(url);
}
addCustomer (customer: Customer): Observable {
return this.http.post(this.customersUrl, customer, httpOptions);
}
deleteCustomer (customer: Customer | number): Observable {
const id = typeof customer === 'number' ? customer : customer.id;
const url = `${this.customersUrl}/${id}`;
return this.http.delete(url, httpOptions);
}
updateCustomer (customer: Customer): Observable {
return this.http.put(this.customersUrl, customer, httpOptions);
}
}
Angular Router
Implement App-Routing module app-routing.module.ts
:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CustomerComponent } from '../customer/customer.component';
import { AddCustomerComponent } from '../add-customer/add-customer.component';
import { CustomerDetailsComponent } from '../customer-details/customer-details.component';
const routes: Routes = [
{
path: 'customers',
component: CustomerComponent
},
{
path: 'customer/add',
component: AddCustomerComponent
},
{
path: 'customers/:id',
component: CustomerDetailsComponent
},
{
path: '',
redirectTo: 'customers',
pathMatch: 'full'
},
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
Router Outlet & Router Links
-> Questions:
- How to show Componenets with Angular Routers? -> Solution: using
Router Outlet
- How to handle the routing that comes from user’s actions? (like clicks on anchor tag) -> Solution: using
Router Link
-> We can achieve above functions by using Angular’s router-outlet
and routerLink
.
Modify the template file app.component.html
of AppComponenet
component as below:
<div class="container">
<div class="row">
<div class="col-sm-4">
<h1>Angular HttpClient</h1>
<ul class="nav justify-content-center">
<li class="nav-item">
<a routerLink="customers" class="btn btn-light btn-sm" role="button" routerLinkActive="active">Retrieve</a>
</li>
<li class="nav-item">
<a routerLink="customer/add" class="btn btn-light btn-sm" role="button" routerLinkActive="active">Create</a>
</li>
</ul>
<hr>
<router-outlet></router-outlet>
</div>
</div>
</div>
Customer Component
Customer Component
->
- Implement CustomerComponent
class customer.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Customer } from '../customer';
import { CustomerService } from '../customer.service';
@Component({
selector: 'app-customer',
templateUrl: './customer.component.html',
styleUrls: ['./customer.component.css']
})
export class CustomerComponent implements OnInit {
customers: Customer[];
constructor(private customerService: CustomerService) {}
ngOnInit(): void {
this.getCustomers();
}
getCustomers() {
return this.customerService.getCustomers()
.subscribe(
customers => {
console.log(customers);
this.customers = customers
}
);
}
}
- Implement the template customer.component.html
:
<h3>All Customers</h3>
<div *ngFor="let cust of customers">
<a [routerLink]="['/customers', cust.id]" style="color:black"><span class="badge badge-dark">{{cust.id}}</span> -> {{ cust.firstname }}</a>
</div>
Customer Detail Component
Customer Detail
->
-> results:
- Implement CustomerDetails
class customer-details.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Customer } from '../customer';
import { CustomerService } from '../customer.service';
import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';
@Component({
selector: 'app-customer-details',
templateUrl: './customer-details.component.html',
styleUrls: ['./customer-details.component.css']
})
export class CustomerDetailsComponent implements OnInit {
customer = new Customer() ;
submitted = false;
message: string;
constructor(
private customerService: CustomerService,
private route: ActivatedRoute,
private location: Location
) {}
ngOnInit(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.customerService.getCustomer(id)
.subscribe(customer => this.customer = customer);
}
update(): void {
this.submitted = true;
this.customerService.updateCustomer(this.customer)
.subscribe(() => this.message = "Customer Updated Successfully!");
}
delete(): void {
this.submitted = true;
this.customerService.deleteCustomer(this.customer.id)
.subscribe(()=> this.message = "Customer Deleted Successfully!");
}
goBack(): void {
this.location.back();
}
}
- Implement CustomerDetailsComponent
template customer-details.component.html
:
<h4><span class="badge badge-light ">{{customer.id}}</span> -> {{customer.firstname}}</h4>
<div [hidden]="submitted">
<form (ngSubmit)="update()" #detailCustomerForm="ngForm">
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" class="form-control" id="firstname" required
[(ngModel)]="customer.firstname" name="firstname" #firstname="ngModel">
<div [hidden]="firstname.valid || firstname.pristine"
class="alert alert-danger">
First Name is required
</div>
</div>
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" id="lastname" required
[(ngModel)]="customer.lastname" name="lastname" #lastname="ngModel">
<div [hidden]="lastname.valid || lastname.pristine"
class="alert alert-danger">
Last Name is required
</div>
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="number" class="form-control" id="age" required
[(ngModel)]="customer.age" name="age" #age="ngModel">
<div [hidden]="age.valid || age.pristine"
class="alert alert-danger">
Age is required
</div>
</div>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-dark" (click)="goBack()">Back</button>
<button type="submit" class="btn btn-dark" [disabled]="!detailCustomerForm.form.valid">Update</button>
<button class="btn btn-dark" (click)="delete()">Delete</button>
</div>
</form>
</div>
<div [hidden]="!submitted">
<p>{{message}}</p>
<div class="btn-group btn-group-sm">
<button class="btn btn-dark" (click)="goBack()">Back</button>
</div>
</div>
We can change the value of ng-valid
& ng-invalid
for more visual feedback,
-> Create ./assets/forms.css
file:
.ng-valid[required], .ng-valid.required {
border-left: 5px solid rgba(32, 77, 32, 0.623);
}
.ng-invalid:not(form) {
border-left: 5px solid rgb(148, 27, 27);
}
Add ./assets/forms.css
file to index.html
:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular6Httpclient</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="assets/forms.css">
</head>
<body>
<app-root></app-root>
</body>
</html>
Add-Customer Component
AddCustomer Component
->
-> result:
- Implement AddCustomerComponent
class add-customer.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Customer } from '../customer';
import { CustomerService } from '../customer.service';
import { Location } from '@angular/common';
@Component({
selector: 'app-add-customer',
templateUrl: './add-customer.component.html',
styleUrls: ['./add-customer.component.css']
})
export class AddCustomerComponent{
customer = new Customer();
submitted = false;
constructor(
private customerService: CustomerService,
private location: Location
) { }
newCustomer(): void {
this.submitted = false;
this.customer = new Customer();
}
addCustomer() {
this.submitted = true;
this.save();
}
goBack(): void {
this.location.back();
}
private save(): void {
this.customerService.addCustomer(this.customer)
.subscribe();
}
}
- Implement the template add-customer.component.html
:
<h3>Add Customer</h3>
<div [hidden]="submitted">
<form #addCustomerForm="ngForm">
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" class="form-control" id="firstname" placeholder="Give Customer's FirstName"
required
[(ngModel)]="customer.firstname" name="firstname" #firstname="ngModel">
<div [hidden]="firstname.valid || firstname.pristine"
class="alert alert-danger">
First Name is required
</div>
</div>
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" id="lastname" placeholder="Give Customer's LastName"
required
[(ngModel)]="customer.lastname" name="lastname" #lastname="ngModel">
<div [hidden]="lastname.valid || lastname.pristine"
class="alert alert-danger">
Last Name is required
</div>
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="number" class="form-control" id="age"
placeholder="Give Customer's Age"
required
[(ngModel)]="customer.age" name="age" #age="ngModel">
<div [hidden]="age.valid || age.pristine"
class="alert alert-danger">
Age is required
</div>
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-dark" (click)="goBack()">Back</button>
<button class="btn btn-dark" (click)="addCustomer()" [disabled]="!addCustomerForm.form.valid">Add</button>
</div>
</form>
</div>
<div [hidden]="!submitted">
<p>Submitted Successfully! -> <span class="badge badge-light">{{customer.firstname}} {{customer.lastname}}</span></p>
<div class="btn-group btn-group-sm">
<button class="btn btn-dark" (click)="goBack()">Back</button>
<button class="btn btn-dark" (click)="newCustomer(); addCustomerForm.reset()">Continue</button>
</div>
</div>
SourceCode
- Angular-6-Http-Client
- SpringBootRestAPIs