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
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 Mapcustomers = 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