CRUD application using angular 4 spring rest services spring data jpa

We want to create a simple app using which we will be able to Create, Update and Delete bikes.

  • We should be able to enter bike model and bike manufacturer in a form and should be able to submit.
  • Created bikes should be shown in a list.
  • On click of bike model, a separate page should open which helps us edit bike information.
  • Just beside bike model, Delete button should be able to delete bike.

I would recommend going through below articles for easy understanding of crud application using angular 4 spring rest services spring data jpa.

  • Angular CRUD example : It has the same functionality as mentioned above. Only difference is it is not connected to any real api. We are going to connect this to real api now.
  • Creating Rest web services in spring: To get an overview of restful web services
  • Creating a full stack environment : Helps understand how you can connect to real API from angular application. We will combine the functionality of all these three in single app.

Technical Components:

  • Client side which runs on node js server.
    • angular html forms and their corresponding components.
    • angular services which connect with real API.
    • proxy configuration to connect to localhost:8080 from localhost:4200
  • Server side i.e. spring boot
    • spring controllers is entry point of application, invoked by calling a url
    • controllers will invoke spring services.
    • spring service will invoke spring data jpa repositories which can persist data in mysql database.

Requirements:

Technical Design:

  • Spring
    • BikeController.java : Accepts requests and serves response. Invokes BikeService
    • BikeService.java : An interface which defines contract for service implementations.
    • BikeServiceImpl.java: Implements BikeService and invokes methods on BikeRepository.
    • BikeRepository: Implements CrudRepository from spring data jpa.
    • Bike.java: Entity class which represents a database table.
    • Application.java: Runs application and starts serving requests.
    • application.properties : configures mysql url and credentials.
    • build.gradle: mentions dependencies for application.
  • Angular
    • bikes.component.html: responsible for showing bike form and list
    • bikes.component.ts: responsible for holding and managing data on html.
    • bike-info.component.html: shows info and enables us edit it.
    • bike-info.component.ts: corresponding to html.
    • bike.ts: model class for bike information.
    • proxy.conf.json : proxy configuration to connect to localhost:8080 from localhost:4200.

CRUD application using angular 4 spring rest services spring data jpa:

Server Application:

BikeController.java

package com.thejavageek.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.thejavageek.model.Bike;
import com.thejavageek.services.BikeService;

@RestController
@RequestMapping("/api/bikes")
public class BikeController {

	@Autowired
	private BikeService bikeService;

	@RequestMapping(value = "/allBikes", method = RequestMethod.GET)
	public List<Bike> getAllBikes() {

		return bikeService.getAllBikes();
	}

	@RequestMapping(value = "/getBike/{bikeId}", method = RequestMethod.GET)
	public Bike getBike(@PathVariable("bikeId") Integer bikeId) {
		return bikeService.getBike(bikeId);
	}

	@RequestMapping(value = "/createBike", method = RequestMethod.POST)
	public Bike createBike(@RequestBody Bike bike) {
		return bikeService.createBike(bike);
	}

	@RequestMapping(value = "/updateBike", method = RequestMethod.POST)
	public Bike updateBike(@RequestBody Bike bike) {
		return bikeService.updateBike(bike);
	}
	
	@RequestMapping(value = "/deleteBike/{bikeId}", method = RequestMethod.GET)
	public void deleteBike(@PathVariable("bikeId") Integer bikeId) {
		bikeService.deleteBike(bikeId);
	}

}

BikeService.java:

package com.thejavageek.services;

import java.util.List;
import com.thejavageek.model.Bike;

public interface BikeService {

	List<Bike> getAllBikes();
	
	Bike getBike(Integer bikeId);
	
	Bike createBike(Bike bike);
	
	Bike updateBike(Bike bike);

	void deleteBike(Integer bikeId);

}

BikeServiceImpl.java

package com.thejavageek.services.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.thejavageek.model.Bike;
import com.thejavageek.repositories.BikeRepository;
import com.thejavageek.services.BikeService;

@Service
public class BikeServiceImpl implements BikeService {

	@Autowired
	private BikeRepository bikeRepository;

	@Override
	public List<Bike> getAllBikes() {
		List<Bike> bikes = new ArrayList<Bike>();
		Iterator<Bike> iterator = bikeRepository.findAll().iterator();
		while (iterator.hasNext()) {
			bikes.add(iterator.next());
		}

		return bikes;
	}

	@Override
	public Bike getBike(Integer bikeId) {
		return bikeRepository.findOne(bikeId);
	}

	@Override
	public Bike createBike(Bike bike) {
		return bikeRepository.save(bike);
	}

	@Override
	public Bike updateBike(Bike bike) {
		return bikeRepository.save(bike);
	}

	@Override
	public void deleteBike(Integer bikeId) {
		bikeRepository.delete(bikeId);
	}

}

BikeRepository.java

package com.thejavageek.repositories;

import java.io.Serializable;
import org.springframework.data.repository.CrudRepository;
import com.thejavageek.model.Bike;

public interface BikeRepository extends CrudRepository<Bike, Serializable> {

}

Bike.java

package com.thejavageek.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Bike {

	@Id
	@GeneratedValue(strategy = GenerationType.TABLE )
	private Integer id;
	private String model;
	private String manufacturer;

	public Bike(){
		
	}
	
	public Bike(Integer id, String model, String manufacturer) {
		this.id = id;
		this.model = model;
		this.manufacturer = manufacturer;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getModel() {
		return model;
	}

	public void setModel(String model) {
		this.model = model;
	}

	public String getManufacturer() {
		return manufacturer;
	}

	public void setManufacturer(String manufacturer) {
		this.manufacturer = manufacturer;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((manufacturer == null) ? 0 : manufacturer.hashCode());
		result = prime * result + ((model == null) ? 0 : model.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Bike other = (Bike) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		if (manufacturer == null) {
			if (other.manufacturer != null)
				return false;
		} else if (!manufacturer.equals(other.manufacturer))
			return false;
		if (model == null) {
			if (other.model != null)
				return false;
		} else if (!model.equals(other.model))
			return false;
		return true;
	}
	
	

}

Application.java

package com.thejavageek;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
public class Application {

	public static void main(String args[]) {
		SpringApplication.run(Application.class, args);
	}

}

application.properties:

spring.datasource.url = jdbc:mysql://localhost:3306/<yourDBName>
spring.datasource.username = <dbUserName>
spring.datasource.password = <dbPassword>

# Show or not log for each sql query
spring.jpa.show-sql = true

# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update

# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy

# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
# stripped before adding them to the entity manager)

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

build.gradle:

buildscript {
	ext {
		springBootVersion = '1.5.2.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.boot:spring-boot-starter-data-jpa')
	compile("mysql:mysql-connector-java:5.1.13")
	compile("org.springframework.boot:spring-boot-starter-web")
	compile('org.springframework.boot:spring-boot-starter-jersey')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

Client Application:

app-routing.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';

import { BikeInfoComponent } from '../bike-info/bike-info.component';
import { BikesComponent } from '../bikes/bikes.component';

const routes: Routes = [
  { path: 'information/:id', component: BikeInfoComponent },
  { path: 'bikes', component: BikesComponent }
];

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

bike-info.component.html:

<md-card *ngIf="bike" style="width: 30%">
  <md-input-container>
    <input mdInput [(ngModel)]="bike.model" placeholder="Model" name="model">
  </md-input-container><br>
  <md-input-container>
    <input mdInput [(ngModel)]="bike.manufacturer" placeholder="Manufacturer" name="manufacturer">
  </md-input-container><br>
  <md-card-actions>
    <button md-button (click)="goBack()"> Back </button>
    <button md-button (click)="updateBike()"> Update </button>
  </md-card-actions>
</md-card>

bike-info.component.ts:

import ‘rxjs/add/operator/switchMap’
import { Component, OnInit, Input } from ‘@angular/core’;
import { ActivatedRoute, Params } from ‘@angular/router’;
import { Location } from ‘@angular/common’;

import { Bike } from ‘../bike’;
import { BikeService } from ‘../bike.service’

@Component({
selector: ‘app-bike-info’,
templateUrl: ‘./bike-info.component.html’,
styleUrls: [‘./bike-info.component.css’]
})
export class BikeInfoComponent implements OnInit {

bike: Bike;

constructor(
private bikeService: BikeService,
private route: ActivatedRoute,
private location: Location
) { }

ngOnInit(): void {
this.route.params.switchMap((params: Params) => this.bikeService.getBike(+params[‘id’]))
.subscribe(bike => this.bike = bike);
}
updateBike(): void {
this.bikeService.updateBike(this.bike);
this.goBack();
}
goBack(): void {
this.location.back();
}

}

bikes.component.html:

<h2>Bikes</h2>

  <md-input-container style="width:30%">
    <input mdInput [(ngModel)]="newBike.model" placeholder="Model" name="model">
  </md-input-container><br>
  <md-input-container style="width:30%">
    <input mdInput [(ngModel)]="newBike.manufacturer" placeholder="Manufacturer" name="manufacturer">
  </md-input-container><br>
  <button md-raised-button (click)="createBike(newBike)">Create Bike</button>

  <md-list>
    <md-list-item *ngFor="let bike of bikes" (click)="showInfo(bike)">
          {{bike.model}} <button md-raised-button (click)="deleteBike(bike); $event.stopPropagation()">Delete</button>
    </md-list-item>
  </md-list>

bikes.component.ts:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Bike } from '../bike';
import { BikeService } from '../bike.service';

@Component({
  selector: 'app-bikes',
  templateUrl: './bikes.component.html',
  styleUrls: ['./bikes.component.css']
})
export class BikesComponent implements OnInit {

  bikes: Bike[];
  selectedBike: Bike;
  newBike: Bike;

  constructor(private router: Router, private bikeService: BikeService) {

  }

  ngOnInit() {
    this.bikeService.getBikes().then(bikes => this.bikes = bikes);
    this.newBike = new Bike();
  }

  createBike(bike: Bike): void {

    this.bikeService.createBike(bike)
      .then(bike => {
        this.bikes.push(bike);
        this.selectedBike = null;
      });
  }

  deleteBike(bike: Bike): void {
    this.bikeService
      .deleteBike(bike)
      .then(() => {
        this.bikes = this.bikes.filter(h => h !== bike);
        if (this.selectedBike === bike) { this.selectedBike = null; }
      });
  }

  showInfo(bike: Bike): void {
    this.selectedBike = bike;
    this.router.navigate(['/information', this.selectedBike.id]);
  }
}
bike.service.ts:
import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Bike } from './bike'

@Injectable()

export class BikeService {

  constructor(private http: Http) {

  }

  private headers = new Headers({ 'Content-Type': 'application/json' });
  private bikesUrl = '/api/bikes';

  getBikes(): Promise<Bike[]> {
    return this.http.get(this.bikesUrl + "/allBikes")
      .toPromise()
      .then(response => response.json() as Bike[])
      .catch(this.handleError);
  }


  getBike(id: number): Promise<Bike> {
    const url = `${this.bikesUrl}/getBike/${id}`;
    return this.http.get(url)
      .toPromise()
      .then(response => response.json() as Bike)
      .catch(this.handleError);
  }


  createBike(bike: Bike): Promise<Bike> {
    return this.http
      .post(this.bikesUrl + "/createBike", JSON.stringify(bike), { headers: this.headers })
      .toPromise()
      .then(res => res.json() as Bike)
      .catch(this.handleError);
  }

  updateBike(bike: Bike): Promise<Bike> {
    return this.http
      .post(this.bikesUrl + "/updateBike", JSON.stringify(bike), { headers: this.headers })
      .toPromise()
      .then(() => bike)
      .catch(this.handleError);
  }

  deleteBike(bike: Bike): Promise<void> {
    const url = `${this.bikesUrl}/deleteBike/${bike.id}`;
    return this.http.get(url, { headers: this.headers })
      .toPromise()
      .then(() => null)
      .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }
}

bike.ts

export class Bike {
    id: number;
    model: String;
    manufacturer: String;
}

app.component.html:

XHTML

1
<router-outlet></router-outlet>

app.component.ts:
import { Component, OnInit } from '@angular/core';
import { Bike } from './bike';
import { BikeService } from './bike.service';

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


  ngOnInit() {

  }

}

app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { BikeInfoComponent } from './bike-info/bike-info.component';
import { BikesComponent } from './bikes/bikes.component';
import { BikeService } from './bike.service';

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { AppRoutingModule } from './app-routing/app-routing.module';
import { MaterialModule, MdList, MdListItem } from '@angular/material'


@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    MaterialModule.forRoot(),
    AppRoutingModule,
    BrowserAnimationsModule
  ],
  declarations: [
    AppComponent,
    BikesComponent,
    BikeInfoComponent,
  ],
  bootstrap: [AppComponent],
  providers: [BikeService],
})
export class AppModule { }

main.ts:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule);

proxy.conf.json:

1234567{    “/api/*”: {        “target”: “http://localhost:8080”,        “secure”: false,        “logLevel”: “debug”    }}

After all this is done, Open your package.json and change start script from “start”: “ng serve” to “start”: “ng serve –proxy-config proxy.conf.json”  

Your final project structures will look like below.