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:
- JDK installed on your machine. (If not you can refer instructions on official site)
- your favourite java IDE. I am using eclipse for this tutorial.
- Gradle installation.
- Gradle eclipse buildship plugin installed in eclipse.
- Node.js installation. (Node JS helps manage angular dependencies pretty well so it is recommended.)
- Visual Studio Code: Extremely good IDE for angular development.
- MySql database installed on your machine.
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.
