Spring Webflux & Angular
Overview
Spring Reactive web application with angular clarity and & reactive mongo db. Creates uber jar to deploy.
Github: https://github.com/gitorko/project60
Quick Overview
To deploy the application in a single command, clone the project, make sure no conflicting docker containers or ports are running and then run
1git clone https://github.com/gitorko/project60
2cd project60
3docker-compose -f docker/docker-compose.yml up
Features
Clarity is an open source library that provides various Angular components.
Code
1package com.demo.project60;
2
3import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
4import static org.springframework.web.reactive.function.server.RouterFunctions.route;
5import static org.springframework.web.reactive.function.server.ServerResponse.ok;
6
7import java.util.Arrays;
8import java.util.List;
9import java.util.Random;
10
11import com.demo.project60.domain.Customer;
12import com.demo.project60.repository.CustomerRepository;
13import lombok.extern.slf4j.Slf4j;
14import org.springframework.beans.factory.annotation.Value;
15import org.springframework.boot.CommandLineRunner;
16import org.springframework.boot.SpringApplication;
17import org.springframework.boot.autoconfigure.SpringBootApplication;
18import org.springframework.context.annotation.Bean;
19import org.springframework.core.io.Resource;
20import org.springframework.http.MediaType;
21import org.springframework.web.reactive.function.server.RouterFunction;
22import org.springframework.web.reactive.function.server.ServerResponse;
23import reactor.core.publisher.Flux;
24
25@SpringBootApplication
26@Slf4j
27public class Main {
28 public static void main(String[] args) {
29 SpringApplication.run(Main.class, args);
30 }
31
32 @Bean
33 public CommandLineRunner seedData(CustomerRepository customerRepository) {
34 return args -> {
35 log.info("Initializing repo!");
36 List<String> city = Arrays.asList("London", "New York", "Bangalore");
37 Flux<Customer> customers = Flux.range(1, 5).map(i -> {
38 int randomIndex = new Random().nextInt(2 - 0 + 1) + 0;
39 return new Customer(null, "first_" + i, "last_" + i, city.get(randomIndex));
40 });
41 customerRepository.deleteAll()
42 .thenMany(customers.flatMap(customerRepository::save)
43 .thenMany(customerRepository.findAll()))
44 .subscribe(e -> log.info(e.toString()));
45 log.info("Data seed completed!");
46 };
47 }
48}
1package com.demo.project60.repository;
2
3import com.demo.project60.domain.Customer;
4import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
5
6public interface CustomerRepository extends ReactiveMongoRepository<Customer, String> {
7}
1spring:
2 main:
3 banner-mode: "off"
4 data:
5 mongodb:
6 database: test-db
7 username: test
8 password: test@123
9 host: localhost
10 port: 27017
11 authentication-database: admin
1import {Injectable} from '@angular/core';
2import {HttpClient} from '@angular/common/http';
3import {Observable} from 'rxjs';
4import {Customer} from "../models/customer";
5
6@Injectable({
7 providedIn: 'root'
8})
9export class RestService {
10
11 constructor(private http: HttpClient) {
12 }
13
14 public getCustomers(): Observable<Customer[]> {
15 return this.http.get<Customer[]>('/api/customer');
16 }
17
18 public saveCustomer(customer: Customer) {
19 return this.http.post('/api/customer', customer);
20 }
21
22 public deleteCustomer(id: any): Observable<any> {
23 return this.http.delete('/api/customer/' + id);
24 }
25
26 public getTime(): Observable<string> {
27 return this.http.get<string>('/api/time');
28 }
29}
1<div class="content-container">
2 <div class="content-area">
3
4 <div class="clr-row">
5
6 <div class="clr-col-12">
7 <p style="text-align: center">
8 <!-- interpolation & pipe -->
9 Server Time: {{currentTime | date:'dd-MM-yyyy' }}
10 </p>
11
12 <h2 style="text-align: center">Customers</h2>
13
14 <clr-datagrid>
15 <clr-dg-placeholder class="content-center">No Customers!</clr-dg-placeholder>
16 <clr-dg-column [clrDgField]="'id'">ID</clr-dg-column>
17 <clr-dg-column [clrDgField]="'firstName'">First Name</clr-dg-column>
18 <clr-dg-column [clrDgField]="'lastName'">Last Name</clr-dg-column>
19 <clr-dg-column [clrDgField]="'city'">City</clr-dg-column>
20 <clr-dg-column>Action</clr-dg-column>
21 <!-- structural directive -->
22 <clr-dg-row clr-dg-row *clrDgItems="let customer of customers">
23 <clr-dg-cell>{{customer.id}}</clr-dg-cell>
24 <clr-dg-cell>{{customer.firstName}}</clr-dg-cell>
25 <clr-dg-cell>{{customer.lastName}}</clr-dg-cell>
26 <clr-dg-cell>{{customer.city}}</clr-dg-cell>
27 <clr-dg-cell>
28 <cds-icon shape="trash" style="cursor: pointer; color: blue" (click)="deleteCustomer(customer)">
29 </cds-icon>
30 </clr-dg-cell>
31 </clr-dg-row>
32 <clr-dg-footer>{{customers.length}} customers</clr-dg-footer>
33 </clr-datagrid>
34
35 <div class="clr-col-12">
36 <form class="clr-form clr-form-horizontal">
37 <div class="clr-form-control">
38 <label for="firstName" class="clr-control-label">First Name</label>
39 <div class="clr-control-container">
40 <div class="clr-input-wrapper">
41 <!-- two way data binding -->
42 <input type="text" [(ngModel)]="customer.firstName" id="firstName" name="firstName"
43 placeholder="Placeholder" class="clr-input"/>
44 </div>
45 </div>
46 </div>
47 <div class="clr-form-control">
48 <label for="lastName" class="clr-control-label">Last Name</label>
49 <div class="clr-control-container">
50 <div class="clr-input-wrapper">
51 <input [(ngModel)]="customer.lastName" type="text" id="lastName" name="lastName"
52 placeholder="Placeholder" class="clr-input"/>
53 </div>
54 </div>
55 </div>
56 <div class="clr-form-control">
57 <div class="clr-control-container">
58 <!-- event binding -->
59 <button type="submit" class="btn btn-primary" (click)="saveCustomer()">Save</button>
60 </div>
61 </div>
62 </form>
63 </div>
64
65 </div>
66 </div>
67 </div>
68</div>
1import {Component, OnInit} from '@angular/core';
2import {Customer} from "../models/customer";
3import {RestService} from "../services/rest.service";
4import {ClarityIcons, trashIcon} from "@cds/core/icon";
5
6@Component({
7 selector: 'app-home',
8 templateUrl: './home.component.html',
9 styleUrls: ['./home.component.css']
10})
11export class HomeComponent implements OnInit {
12
13 customers: Customer[] = [];
14 customer: Customer = new Customer();
15 currentTime = '';
16
17 constructor(private restService: RestService) {
18 ClarityIcons.addIcons(trashIcon);
19 }
20
21 ngOnInit() {
22 this.getCustomers();
23 }
24
25 getCustomers(): void {
26 this.customer = new Customer();
27 this.restService.getCustomers().subscribe(data => {
28 this.customers = data;
29 });
30 this.restService.getTime().subscribe(data => {
31 this.currentTime = data;
32 });
33 }
34
35 saveCustomer(): void {
36 this.restService.saveCustomer(this.customer)
37 .subscribe(data => {
38 this.getCustomers();
39 }, error => {
40 console.log(error);
41 });
42 }
43
44 deleteCustomer(customer: Customer): void {
45 console.log('delete: ' + customer.id);
46 this.restService.deleteCustomer(customer.id)
47 .subscribe(data => {
48 this.getCustomers();
49 }, error => {
50 console.log(error);
51 });
52 }
53
54}
Setup
1# Project 60
2
3Spring WebFlux & Angular, Reactive MongoDB, Clarity, Docker
4
5[https://gitorko.github.io/spring-webflux-angular/](https://gitorko.github.io/spring-webflux-angular/)
6
7### Version
8
9Check version
10
11```bash
12$java --version
13openjdk 17.0.3 2022-04-19 LTS
14
15node --version
16v16.16.0
17
18yarn --version
191.22.18
20```
21
22### Mongo DB
23
24```bash
25docker run --name my-mongo -e MONGO_INITDB_ROOT_USERNAME=test -e MONGO_INITDB_ROOT_PASSWORD=test@123 -p 27017:27017 -d mongo
26docker ps
27```
28
29### Dev
30
31To run the backend in dev mode.
32
33```bash
34./gradlew clean build
35./gradlew bootRun
36```
37
38To Run UI in dev mode
39
40```bash
41cd ui
42yarn install
43yarn build
44yarn start
45```
46
47Open [http://localhost:4200/](http://localhost:4200/)
48
49### Prod
50
51To run as a single jar, both UI and backend are bundled to single uber jar.
52
53```bash
54./gradlew cleanBuild
55cd build/libs
56java -jar project60-1.0.0.jar
57```
58
59Open [http://localhost:8080/](http://localhost:8080/)
60
61### Docker
62
63```bash
64./gradlew cleanBuild
65docker build -f docker/Dockerfile --force-rm -t project60:1.0.0 .
66docker images |grep project60
67docker tag project60:1.0.0 gitorko/project60:1.0.0
68docker push gitorko/project60:1.0.0
69docker-compose -f docker/docker-compose.yml up
70```
71
72## Commands
73
74```bash
75ng new ui
76cd ui
77yarn add @cds/core @clr/icons @clr/angular @clr/ui
78```
79
80proxy.config.json redirects the client calls
81
82```json
83{
84 "/api/*": {
85 "target": "http://localhost:8080/",
86 "secure": false,
87 "logLevel": "debug"
88 }
89}
90```
91
92Modify package.json file, change the start & build command to
93
94```bash
95"start": "ng serve --proxy-config proxy.config.json --open",
96"build": "ng build --prod",
97```
98
99Update the routing.The useHash:true will be useful when we deploy the application in a single uber jar later.
100If we dont use this then the back button on the application will run into errors. It uses a hash based routing instead of the default location based routing.
101
102If you run into the error
103
104```bash
105Error: initial exceeded maximum budget.
106```
107
108Update the budget in angular.json file
109
110```
111"maximumWarning": "4mb",
112"maximumError": "5mb"
113```
References
comments powered by Disqus