Thursday, January 7, 2021

Angular HttpClient to Communicate With Backend Service

In this tutorial we’ll see how to communicate with a back end service using the Angular HttpClient. As an example we’ll take a Spring Boot REST API crud application as a back end service and connect to it from an Angular front end application using the Angular HttpClient.

In this post stress is more on explaining how to use Angular client HTTP API, how to make a request to back end service and how to catch errors.

Back end Spring Boot service

In the Spring Boot application we have a Rest Controller class with handler mappings for different CRUD operations. DB used is MySQL and the DB table is User.

To get more details and code for the Spring Boot CRUD application, refer this post- Spring Boot REST API CRUD Example With Spring Data JPA

Only change that is required in the Spring boot application is to add @CrossOrigin annotation in the Controller class to enable cross-origin resource sharing (CORS). If you won't use this annotation cross browser HTTP request won't be allowed and the Spring Boot application won't accept any request coming from http://localhost:4200

@RestController
@CrossOrigin(origins="http://localhost:4200")  
@RequestMapping("/user")
public class UserController {
  @Autowired
  UserService userService;
  // Insert user record
  @PostMapping
  @ResponseStatus(HttpStatus.CREATED)
  public User addEmployee(@RequestBody User user) {
    return userService.addUser(user);
  }
  // Fetch all user records
  @GetMapping
  public List<User> getAllUsers(){
    return userService.getAllUsers();
  }
  // Fetch single user
  @GetMapping("/{id}")
  public User getUserById(@PathVariable("id") int userId){
    return userService.getUserById(userId);
  }
  // Update user record
  @PutMapping("/updateuser")
  public ResponseEntity<String> updateUser(@RequestBody User user) {  
    try {
      userService.updateUser(user);
      return new ResponseEntity<String>(HttpStatus.OK);
    }catch(NoSuchElementException ex){
      // log the error message
      System.out.println(ex.getMessage());
      return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
    }
  }
  // Delete user record
  @DeleteMapping("/{id}")
  public ResponseEntity<String> deleteUser(@PathVariable int id){
    try {
      userService.deleteUserById(id);
      return new ResponseEntity<String>(HttpStatus.OK);
    }catch(RuntimeException ex){
      // log the error message
      System.out.println(ex.getMessage());
      return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
    }
  }
}

Angular application

1. Model class

In the front end application we’ll need a model class with the fields that map with the fields of the User entity class in the Spring Boot application.

user.model.ts

export class User {
  id: number;
  firstName: String;
  lastName: String;
  userType: String
  startDate: Date;
}

2. Add required modules in app.module.ts

In the application we’ll be having routing so we’ll import a routing file, there are forms to be created so we’ll import ReactiveFormsModule and HttpClientModule in order to perform http requests.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule} from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AddUserComponent } from './users/adduser/adduser.component';
import { DeleteUserComponent } from './users/deleteuser/deleteuser.component';

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

Here AddUserComponent and DeleteUserComponent are component classes that will be created for adding and deleting user respectively.

3. App routing module

Routing module which maps path to the component.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AddUserComponent } from './users/adduser/adduser.component';
import { DeleteUserComponent } from './users/deleteuser/deleteuser.component';

const routes: Routes = [
      {path: 'adduser', component: AddUserComponent},
      {path: 'deleteuser', component: DeleteUserComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule], 
})
export class AppRoutingModule { }

4. Root Component (app.component.ts)

This component is the starting point and used to point to the template that creates menu giving option to add and delete user. Note that Bootstrap classes are used for styling the menu.

Refer this post to know how to add Bootstrap to Angular application- How to Add Bootstrap to Angular Application

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

}

app.component.html

<nav class="navbar navbar-expand-md bg-dark navbar-dark">
  <div class="container-fluid">
    <div class="collapse navbar-collapse" id="collapsibleNavbar">
      <ul class="nav navbar-nav">
        <li class="nav-item" routerLinkActive="active">
          <a class="nav-link" routerLink="/home">Home</a>
        </li>
        <li class="nav-item" routerLinkActive="active">
          <a class="nav-link" routerLink="/adduser">Add User</a>
        </li>
        <li class="nav-item" routerLinkActive="active">
          <a class="nav-link" routerLink="/deleteuser">Delete User</a>
        </li>
      </ul>
    </div>
  </div>
</nav>
<div class="container">
  <div class="row"><p></p></div>
  <div class="row">
    <div class="col-md-12">
      <router-outlet></router-outlet>
    </div>
  </div>
</div>

5. Service class (user.service.ts)

Functionality to send a request and accept response is encapsulated in a Service class.

import { User } from '../users/user.model';
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class UserService {
  private baseUrl = 'http://localhost:8080/user'; 
  constructor(private http: HttpClient){}
  addUser(user: User): Observable<User>{
    return this.http.post<User>(this.baseUrl, user);
  }
  deleteUser(id: number){
    return this.http.delete(`${this.baseUrl}/${id}`)
                      .pipe(catchError(this.handleError));
  }

  private handleError(httpError: HttpErrorResponse) {
    if (httpError.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', httpError.error.message);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong.
      console.error(
        `Backend returned code ${httpError.status}, ` +
        `body was: ${httpError.error}`);
    }
    // Return an observable with a user-facing error message.
    return throwError('Something bad happened; please try again later.');
  }
}

Important points to note here are-

  1. Service class uses HttpClient class which has methods to perform HTTP requests. HttpClient supports HTTP methods such as PUT, POST, and DELETE, which you can use to modify the remote data and get method to fetch data from a server. Instance of HttpClient is injected in Service class.
  2. We are making a request to the UserController in the Spring Boot application which handles any request having path as user so the base url is assigned as 'http://localhost:8080/user'
  3. In the service class there is addUser() method to insert a User and deleteUser() method to delete user record.
  4. In addUser() method http.post() method is used which takes 3 parameters-
    • resource URL
    • body - The data to POST in the body of the request.
    • options - An object containing method options
  5. addUser() method takes User object as an argument which is then passed as the body of the request through the post method.
  6. In the deleteUser() method http.delete() method is used where user id is also passed in the request URL. This corresponds to the deleteUser() method of the Controller class in the Spring Boot application where DeleteMapping with id as PathVariable is used.
  7. All the HttpClient methods get, put, post, delete return an Observable and you must subscribe to this Observable.
  8. You must call subscribe() or nothing happens. Actual Request operation is initiated only when you subscribe to the returned Observable. You can subscribe with in the Service class when the method is called or return and subscribe in the Component class. In our example From Service class Observable is returned and the subscription is done in Component classes.
  9. If the request fails on the server, HttpClient returns an error object instead of a successful response.Two types of errors can occur.
    • The server backend might reject the request, returning an HTTP response with a status code such as 404 or 500. These are error responses.
    • Something could go wrong on the client-side such as a network error that prevents the request from completing successfully or an exception thrown in an RxJS operator. These errors produce JavaScript ErrorEvent objects.
    HttpClient captures both kinds of errors in its HttpErrorResponse.
  10. handleError() method in the Service class is used to check the returned error. Error handler also returns an RxJS ErrorObservable with a user-friendly error message.
  11. Delete methos uses a pipe to send all observables returned by the HttpClient.delete() call to the error handler.
  12. In subscribe you can have a second argument which is a function that triggers whenever an error is thrown. In the component classes you will see how that is used to handle errors.

6. Adding new user

For adding a new user there is adduser.component.ts class.

import { Component, OnInit} from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { DatePipe } from '@angular/common';
import { User } from '../user.model';
import { UserService } from 'src/app/services/user.service';

@Component({
  selector: 'app-adduser',
  templateUrl: './adduser.component.html',
  styleUrls: ['./adduser.component.css'],
  providers: [DatePipe]
})
export class AddUserComponent implements OnInit{
  user: User = new User();
  isAdded = false;
  constructor(private userService: UserService, private datePipe: DatePipe){}
  userTypes = ['Silver', 'Gold', 'Platinum'];  
  currentDate = new Date();
  userForm: FormGroup;          
  ngOnInit() {
    this.userForm = new FormGroup({
      firstName: new FormControl('', [Validators.required, Validators.minLength(5)]),
      lastName: new FormControl('', [Validators.required, Validators.minLength(3)]),     
      userType: new FormControl(),
      startDate: new FormControl(this.datePipe.transform(this.currentDate, 'yyyy-MM-dd'))
    });
  }

  onSubmit(){
    this.user.firstName = this.userForm.value.firstName;
    this.user.lastName = this.userForm.value.lastName;
    this.user.userType = this.userForm.value.userType; 
    this.user.startDate = this.userForm.value.startDate;
    this.save();
  }

  save(){
    this.userService.addUser(this.user)
                    .subscribe(user=> {console.log(user);
                      this.isAdded = true;
                    }, error=>console.log(error))
  }
  resetUserForm(){
    this.isAdded = false;
    this.userForm.reset();
  }
}

Important points to note here are-

  1. Instances of UserService class and DatePipe are injected in this component.
  2. DatePipe is used to transform the date to the required format.
  3. Angular Reactive form is used here so FormGroup and the FormControls are created with in the component.
  4. With in the FormControls, Validators are also provided.
  5. userTypes array is used to provide options for the user type dropdown in the form.
  6. Start date field is pre-filled with the current date.
  7. On submitting the form a user object is created from the entered values and then save function is called. From the save function addUser(0 method of the UserService class is called.
  8. You also subscribe to the returned Observable. With in the subscribe first argument is a function that logs the added user to the console and set isAdded to true, second argument is a function to log the error.

Template(adduser.component.html)

<div class="row">
    <div class="col-sm-6, col-md-6">
    <h2>User Registration</h2>
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
        <div class="form-group">
            <label for="firstName">First Name</label>
            <input type="text" class="form-control" id="firstName"
              formControlName="firstName">       
            <div class="alert alert-danger" *ngIf="userForm.get('firstName').invalid 
              && userForm.get('firstName').touched">
              <div *ngIf="userForm.get('firstName').errors.required">
                First Name is required.
              </div>
              <div *ngIf="userForm.get('firstName').errors.minlength">
                First Name must be at least 5 characters long.
              </div>
            </div>                      
          </div>
          <div class="form-group">
            <label for="lastName">Last Name</label>
            <input type="text" class="form-control" id="lastName"
              formControlName="lastName">       
            <div class="alert alert-danger" *ngIf="userForm.get('lastName').invalid 
              && userForm.get('lastName').touched">
              <div *ngIf="userForm.get('lastName').errors.required">
                Last Name is required.
              </div>
              <div *ngIf="userForm.get('lastName').errors.minlength">
                Last Name must be at least 3 characters long.
              </div>
            </div>                      
          </div>
          <div class="form-group">
            <label for="startDate">Start Date</label>
            <input type="date" class="form-control" id="startDate"
            formControlName="startDate">                        
          </div>
          <div class="form-group">
            <label for="userType">User Type</label>
            <select class="form-control" id="userType"                    
            formControlName="userType">
              <option *ngFor="let type of userTypes" [value]="type">{{type}}</option>
            </select>
          </div>
          <button type="submit" [disabled]="userForm.invalid" class="btn btn-success">Submit</button>
        </form>
        <div class="row"> 
          <div class="col-sm-6, col-md-6">
            <div class="alert alert-info" *ngIf=isAdded>
              <p>User Added SuccessFully!</p>
              <button class="btn btn-primary" (click)="resetUserForm()">Add another user</button>
            </div>
          </div>  
        </div>
    </div>
</div>
Angulat HttpClient POST example

After adding-

Angulat HttpClient example

7. Deleting a user

For deleting a user there is deleteuser.component.ts class.

import { Component, OnInit } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { UserService } from "src/app/services/user.service";

@Component({
  selector: 'app-deleteuser',
  templateUrl: './deleteuser.component.html'
})
export class DeleteUserComponent implements OnInit{
  userDeleteForm: FormGroup;
  errorMsg = null;
  constructor(private userService: UserService){}
  ngOnInit(): void {
    this.userDeleteForm = new FormGroup({
      userId: new FormControl('', Validators.required),
    });
  }
  onSubmit(){
    this.errorMsg = null;
    this.userService.deleteUser(+this.userDeleteForm.value.userId)
                    .subscribe(user=> console.log(user), error=>{
                      this.errorMsg = error
                    });
  }
}

Important points to note here are-

  1. In the onSubmit() function deleteUser() method of the Service class is called. UserId is passed here after converting it to number.
  2. You also subscribe to the returned Observable which is mandatory to initiate the request. With in the subscribe first argument is a function that logs the user to the console, second argument is a function to handle the error.
  3. For delete we used catchError and an error handler method to return an error message. In the second argument of the subscribe that error message is assigned to the field errorMsg of the Component.

Template (deleteuser.component.html)

<div class="row">
    <div class="col-sm-6, col-md-6">
        <h2>Delete User</h2>
        <form [formGroup]="userDeleteForm" (ngSubmit)="onSubmit()">
            <div class="form-group">
                <label for="userId">Enter Id to be deleted</label>
                <input type="text" class="form-control" id="userId"
                formControlName="userId">       
                    <div class="alert alert-danger" *ngIf="userDeleteForm.get('userId').invalid 
                        && userDeleteForm.get('userId').touched">
                        Please enter a valid Id
                    </div>                                        
            </div>
            <button type="submit" [disabled]="userDeleteForm.invalid" class="btn btn-success">Submit</button>
            
        </form> 
    </div>
</div>
<div class="row"> 
    <div class="col-sm-6, col-md-6">
        <div class="alert alert-danger" *ngIf="errorMsg">
            <p>{{ errorMsg }}</p>
        </div>
    </div>
</div>
Angular HttpClient Delete Example

With error message

Angular HttpClient Spring Boot

8. CSS class

There is also a CSS class to show a green or red border along with the form fields to signify a required field and a field having some error.

src/assets/forms.css

.ng-invalid.ng-untouched:not(form)  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid.ng-touched:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

In the index.html file, update the <head> tag to include the new style sheet.

<link rel="stylesheet" href="assets/forms.css">

That's all for this topic Angulat HttpClient to Communicate With Backend Service. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Angular HttpClient - Set Response Type as Text
  2. Angular Routing Concepts With Example
  3. How to Create a Custom Observable in Angular
  4. Angular Reactive Form Validation Example
  5. Angular Template-Driven Form Validation Example

You may also like-

  1. Angular One-Way Data Binding Using String Interpolation
  2. Angular Two-Way Data Binding With Examples
  3. Pure and Impure Pipes in Angular
  4. CanDeactivate Guard in Angular With Example
  5. Parallel Stream in Java Stream API
  6. Array in Java With Examples
  7. @Import Annotation in Spring JavaConfig
  8. Spring MVC - Binding List of Objects Example

No comments:

Post a Comment