Angular 10 Spring WebFlux CRUD RestAPI

In this tutorial, we’re gonna build a full Reactive Application in which, Spring WebFlux, Spring Data Reactive MongoDB are used for backend, and Angular, RxJS, EventSource are on client side.

Related Posts:
How to use Angular Http Client to fetch Data from SpringBoot RestAPI – Angular 10
How to use Angular HttpClient to POST, PUT, DELETE data on SpringBoot Rest APIs – Angular 10
How to build SpringBoot MongoDb RestfulApi
How to use SpringData MongoRepository to interact with MongoDB
Angular 10 + Spring Boot + MongoDB CRUD example
Introduction to RxJS – Extensions for JavaScript Reactive Streams

I. Technologies

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite 3.9.0.RELEASE
– Spring Boot 2.0.0.RELEASE
– Angular 10
– RxJS 5.1.0
– MongoDB 3.4.10

II. Overview

1. Full Stack Architecture

angular-4-spring-webflux-reactive-mongodb-architecture

2. Reactive Spring Boot Server

2.1 Dependency

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

2.2 Reactive Repository

We just need to create an interface that extends ReactiveCrudRepository to do CRUD operations for a specific type. This repository follows reactive paradigms and uses Project Reactor types (Flux, Mono) which are built on top of Reactive Streams.

import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;

public interface ReactiveCustomerRepository extends ReactiveCrudRepository<Customer, String> {

	Mono<Customer> findByLastname(String lastname);
	Flux<Customer> findByAge(int age);

	@Query("{ 'firstname': ?0, 'lastname': ?1}")
	Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);
}

2.3 Activate reactive Spring Data MongoDB

Support for reactive Spring Data is activated through an @EnableReactiveMongoRepositories annotation:


import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;

@EnableReactiveMongoRepositories
public class MongoDbReactiveConfig extends AbstractReactiveMongoConfiguration {

	@Override
	public MongoClient reactiveMongoClient() {
		return MongoClients.create();
	}

	@Override
	protected String getDatabaseName() {
		return &quot;jsa_mongodb&quot;;
	}
}

2.4 Call Reactive Repository

We can forward the reactive parameters provided by Spring Web Reactive, pipe them into the repository, get back a Flux/Mono and then work with result in reactive way.

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@CrossOrigin(origins = "http://localhost:4200")
@RestController
public class CustomerController {

	@Autowired
	ReactiveCustomerRepository customerRepository;

	@GetMapping("/customers")
	public Flux<Customer> getAllCustomers() {
		return customerRepository.findAll();
	}

	@PostMapping("/customers/create")
	public Mono<Customer> createCustomer(@Valid @RequestBody Customer customer) {
		return customerRepository.save(customer);
	}

	@PutMapping("/customers/{id}")
	public Mono<ResponseEntity<Customer>> updateCustomer(@PathVariable("id") String id, @RequestBody Customer customer) {
		return customerRepository.findById(id).flatMap(customerData -> {
			customerData.setName(customer.getName());
			customerData.setAge(customer.getAge());
			customerData.setActive(customer.isActive());
			return customerRepository.save(customerData);
		}).map(updatedcustomer -> new ResponseEntity<>(updatedcustomer, HttpStatus.OK))
				.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
	}

	@DeleteMapping("/customers/{id}")
	public ResponseEntity<String> deleteCustomer(@PathVariable("id") String id) {
		try {
			customerRepository.deleteById(id).subscribe();
		} catch (Exception e) {
			return new ResponseEntity<>("Fail to delete!", HttpStatus.EXPECTATION_FAILED);
		}
		return new ResponseEntity<>("Customer has been deleted!", HttpStatus.OK);
	}

	@DeleteMapping("/customers/delete")
	public ResponseEntity<String> deleteAllCustomers() {
		try {
			customerRepository.deleteAll().subscribe();
		} catch (Exception e) {
			return new ResponseEntity<>("Fail to delete!", HttpStatus.EXPECTATION_FAILED);
		}
		return new ResponseEntity<>("All customers have been deleted!", HttpStatus.OK);
	}

	@GetMapping("/customers/findbyname")
	public Flux<Customer> findByName(@RequestParam String name) {
		return customerRepository.findByName(name);
	}
}

In the rest controller methods which are annotated by @RequestMapping, we have used some methods of autowired repository which are implemented interface ReactiveCrudRepository:

public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {

	<S extends T> Mono<S> save(S entity);
	Mono<T> findById(ID id);
	Flux<T> findAll();
	Mono<Void> deleteById(ID id);
	Mono<Void> deleteAll();
	// ...
}

And findByName method that we create in our interface ReactiveCustomerRepository:

public interface ReactiveCustomerRepository extends ReactiveCrudRepository<Customer, String> {
	Flux<Customer> findByName(String name);
}

Remember that we want to connect to backend from a client application deployed in a different port, so we must enable CORS using @CrossOrigin annotation.

3. Reactive Angular Client

3.1 Reactive Service

This service interacts with the backend using Server-Sent Events.

import { Injectable } from '@angular/core';
import { HttpClient, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import * as EventSource from 'eventsource';

@Injectable()
export class CustomerService {

  private baseUrl = 'http://localhost:8080/api/customers';
  private customersList: Customer[] = new Array();
  private customersListSearch: Customer[] = new Array();

  constructor(private http: HttpClient) {
  }

  createCustomer(customer: Object): Observable<Object> {
    return this.http.post(`${this.baseUrl}` + `/create`, customer);
  }

  updateCustomer(id: string, value: any): Observable<Object> {
    return this.http.put(`${this.baseUrl}/${id}`, value);
  }

  deleteCustomer(id: string): Observable<any> {
    return this.http.delete(`${this.baseUrl}/${id}`, { responseType: 'text' });
  }

  getCustomersList(): Observable<any> {
    this.customersList = new Array();

    return Observable.create((observer) => {
      const eventSource = new EventSource(`${this.baseUrl}`);
      eventSource.onmessage = (event) => {
        console.log('eventSource.onmessage: ', event);
        const json = JSON.parse(event.data);
        this.customersList.push(new Customer(json['id'], json['name'], json['age'], json['active']));
        observer.next(this.customersList);
      };
      eventSource.onerror = (error) => observer.error('eventSource.onerror: ' + error);
      return () => eventSource.close();
    });
  }

  deleteAll(): Observable<any> {
    return this.http.delete(`${this.baseUrl}` + `/delete`, { responseType: 'text' });
  }

  findCustomers(name): Observable<any> {
    this.customersListSearch = new Array();

    return Observable.create((observer) => {
      const eventSource = new EventSource(`${this.baseUrl}` + `/findbyname?name=` + name);
      eventSource.onmessage = (event) => {
        console.log('eventSource.onmessage: ', event);
        const json = JSON.parse(event.data);
        this.customersListSearch.push(new Customer(json['id'], json['name'], json['age'], json['active']));
        observer.next(this.customersListSearch);
      };
      eventSource.onerror = (error) => observer.error('eventSource.onerror: ' + error);
      return () => eventSource.close();
    });
  }
}

Whenever we receive an event through the EventSource object, onmessage() is invoked. That’s where we parse data and update item list.

Using RxJS Observable object, any Observer that subscribed to the Observable we created can receive events when the item list gets updated (when calling observer.next(...)).

For more details about RxJS, please visit:
Introduction to RxJS – Extensions for JavaScript Reactive Streams

3.2 Reactive Component

This Component calls Service above and keep result inside an Observable object:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';

import { CustomerService } from '../customer.service';
import { Customer } from '../customer';

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

  customers: Observable<Customer[]>;

  constructor(private customerService: CustomerService) {  }

  ngOnInit() {
    this.reloadData();
  }

  deleteCustomers() {
    this.customerService.deleteAll()
      .subscribe(
        data => console.log(data),
        error => console.log('ERROR: ' + error)
      );
  }

  reloadData() {
    this.customers = this.customerService.getCustomersList();
  }
}

In HTML template, we add async pipe that subscribes to the Observable and update component whenever a new event comes:

<div *ngFor="let customer of customers | async">
	<customer-details [customer]='customer'></customer-details>
</div>

<div>
	<button type="button" class="button btn-danger" (click)='deleteCustomers()'>Delete All</button>
</div>

III. Practice

1. Reactive Spring Boot Server

1.1 Project Structure

angular-4-spring-webflux-reactive-mongodb-spring-boot-server-structure

– Class Customer corresponds to document in customer collection.
ReactiveCustomerRepository is an interface extends ReactiveCrudRepository, will be autowired in CustomerController for implementing repository methods.
CustomerController is a REST Controller which has request mapping methods for RESTful requests such as: getAll, create, update, delete Customers.
– Configuration for Spring Data MongoDB properties in application.properties
– Dependencies for Spring Boot WebFlux and Spring Data MongoDB in pom.xml

1.2 Dependency

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

1.3 Data Model


package com.javasampleapproach.reactive.mongodb.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "customer")
public class Customer {
	@Id
	private String id;

	private String name;
	private int age;
	private boolean active;

	public Customer() {
	}

	public Customer(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public String getId() {
		return id;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return this.name;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getAge() {
		return this.age;
	}

	public boolean isActive() {
		return active;
	}

	public void setActive(boolean active) {
		this.active = active;
	}

	@Override
	public String toString() {
		return "Customer [id=" + id + ", name=" + name + ", age=" + age + ", active=" + active + "]";
	}
}

1.4 Reactive Repository

package com.javasampleapproach.reactive.mongodb.repo;

import org.springframework.data.repository.reactive.ReactiveCrudRepository;

import com.javasampleapproach.reactive.mongodb.model.Customer;

import reactor.core.publisher.Flux;

public interface ReactiveCustomerRepository extends ReactiveCrudRepository<Customer, String> {

	Flux<Customer> findByName(String name);
}

1.5 Enable reactive Spring Data MongoDB


package com.javasampleapproach.reactive.mongodb.config;

import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;

@EnableReactiveMongoRepositories
public class MongoDbReactiveConfig extends AbstractReactiveMongoConfiguration {

	@Override
	public MongoClient reactiveMongoClient() {
		return MongoClients.create();
	}

	@Override
	protected String getDatabaseName() {
		return "jsa_mongodb";
	}

}

1.6 REST Controller


package com.javasampleapproach.reactive.mongodb.controller;

import java.time.Duration;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.javasampleapproach.reactive.mongodb.model.Customer;
import com.javasampleapproach.reactive.mongodb.repo.ReactiveCustomerRepository;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping(value = "/api")
public class CustomerController {

	@Autowired
	ReactiveCustomerRepository customerRepository;

	@GetMapping("/customers")
	public Flux getAllCustomers() {
		System.out.println("Get all Customers...");

		return customerRepository.findAll().delayElements(Duration.ofMillis(1000));
	}

	@PostMapping("/customers/create")
	public Mono createCustomer(@Valid @RequestBody Customer customer) {
		System.out.println("Create Customer: " + customer.getName() + "...");

		customer.setActive(false);
		return customerRepository.save(customer);
	}

	@PutMapping("/customers/{id}")
	public Mono> updateCustomer(@PathVariable("id") String id,
			@RequestBody Customer customer) {
		System.out.println("Update Customer with ID = " + id + "...");

		return customerRepository.findById(id).flatMap(customerData -> {
			customerData.setName(customer.getName());
			customerData.setAge(customer.getAge());
			customerData.setActive(customer.isActive());
			return customerRepository.save(customerData);
		}).map(updatedcustomer -> new ResponseEntity<>(updatedcustomer, HttpStatus.OK))
				.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
	}

	@DeleteMapping("/customers/{id}")
	public ResponseEntity deleteCustomer(@PathVariable("id") String id) {
		System.out.println("Delete Customer with ID = " + id + "...");

		try {
			customerRepository.deleteById(id).subscribe();
		} catch (Exception e) {
			return new ResponseEntity<>("Fail to delete!", HttpStatus.EXPECTATION_FAILED);
		}

		return new ResponseEntity<>("Customer has been deleted!", HttpStatus.OK);
	}

	@DeleteMapping("/customers/delete")
	public ResponseEntity deleteAllCustomers() {
		System.out.println("Delete All Customers...");

		try {
			customerRepository.deleteAll().subscribe();
		} catch (Exception e) {
			return new ResponseEntity<>("Fail to delete!", HttpStatus.EXPECTATION_FAILED);
		}

		return new ResponseEntity<>("All customers have been deleted!", HttpStatus.OK);
	}

	@GetMapping("/customers/findbyname")
	public Flux findByName(@RequestParam String name) {

		return customerRepository.findByName(name).delayElements(Duration.ofMillis(1000));
	}
}

To make the result live, we use delayElements(). It causes a delayed time between 2 events.

1.7 Configuration for Spring Data MongoDB

application.properties


spring.data.mongodb.database=jsa_mongodb
spring.data.mongodb.port=27017

2. Reactive Angular Client

2.1 User Interface

angular-4-spring-webflux-reactive-mongodb-angular-client-ui

2.2 Project Structure

angular-4-spring-webflux-reactive-mongodb-angular-client-structure

In this example, we have:
– 4 components: customers-list, customer-details, create-customer, search-customers.
– 3 modules: FormsModule, HttpClientModule, AppRoutingModule.
customer.ts: class Customer (id, name, age, active).
customer.service.ts: Service for HttpClient methods.

2.3 AppModule

app.module.ts


import { AppRoutingModule } from './app-routing.module';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

import { CustomersListComponent } from './customers/customers-list/customers-list.component';
import { CustomerDetailsComponent } from './customers/customer-details/customer-details.component';
import { CreateCustomerComponent } from './customers/create-customer/create-customer.component';
import { SearchCustomersComponent } from './customers/search-customers/search-customers.component';

import { CustomerService } from './customers/customer.service';

@NgModule({
  declarations: [
    AppComponent,
    CustomersListComponent,
    CustomerDetailsComponent,
    CreateCustomerComponent,
    SearchCustomersComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [CustomerService],
  bootstrap: [AppComponent]
})

export class AppModule { }

2.4 Model

customer.ts


export class Customer {
  id: string;
  name: string;
  age: number;
  active: boolean;

  constructor(id?: string, name?: string, age?: number, active?: boolean) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.active = active;
  }
}

2.5 Service

customer.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import * as EventSource from 'eventsource';

import { Customer } from './customer';

@Injectable()
export class CustomerService {

  private baseUrl = 'http://localhost:8080/api/customers';
  private customersList: Customer[] = new Array();
  private customersListSearch: Customer[] = new Array();

  constructor(private http: HttpClient) {
  }

  createCustomer(customer: Object): Observable<Object> {
    return this.http.post(`${this.baseUrl}` + `/create`, customer);
  }

  updateCustomer(id: string, value: any): Observable<Object> {
    return this.http.put(`${this.baseUrl}/${id}`, value);
  }

  deleteCustomer(id: string): Observable<any> {
    return this.http.delete(`${this.baseUrl}/${id}`, { responseType: 'text' });
  }

  getCustomersList(): Observable<any> {
    this.customersList = new Array();

    return Observable.create((observer) => {
      const eventSource = new EventSource(`${this.baseUrl}`);
      eventSource.onmessage = (event) => {
        console.log('eventSource.onmessage: ', event);
        const json = JSON.parse(event.data);
        this.customersList.push(new Customer(json['id'], json['name'], json['age'], json['active']));
        observer.next(this.customersList);
      };

      eventSource.onerror = (error) => observer.error('eventSource.onerror: ' + error);

      return () => eventSource.close();
    });
  }

  deleteAll(): Observable<any> {
    return this.http.delete(`${this.baseUrl}` + `/delete`, { responseType: 'text' });
  }

  findCustomers(name): Observable<any> {
    this.customersListSearch = new Array();

    return Observable.create((observer) => {
      const eventSource = new EventSource(`${this.baseUrl}` + `/findbyname?name=` + name);
      eventSource.onmessage = (event) => {
        console.log('eventSource.onmessage: ', event);
        const json = JSON.parse(event.data);
        this.customersListSearch.push(new Customer(json['id'], json['name'], json['age'], json['active']));
        observer.next(this.customersListSearch);
      };

      eventSource.onerror = (error) => observer.error('eventSource.onerror: ' + error);

      return () => eventSource.close();
    });
  }
}

2.6 Components

2.6.1 CustomerDetailsComponent

customer-details.component.ts

import { Component, OnInit, Input } from '@angular/core';

import { CustomerService } from '../customer.service';
import { Customer } from '../customer';

import { CustomersListComponent } from '../customers-list/customers-list.component';

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

  @Input() customer: Customer;

  constructor(private customerService: CustomerService, private listComponent: CustomersListComponent) { }

  ngOnInit() {
  }

  updateActive(isActive: boolean) {
    this.customerService.updateCustomer(this.customer.id,
      { name: this.customer.name, age: this.customer.age, active: isActive })
      .subscribe(
        data => {
          console.log(data);
          this.customer = data as Customer;
        },
        error => console.log(error)
      );
  }

  deleteCustomer() {
    this.customerService.deleteCustomer(this.customer.id)
      .subscribe(
        data => {
          console.log(data);
          this.listComponent.reloadData();
        },
        error => console.log(error)
      );
  }

}

customer-details.component.html

<div *ngIf="customer">
	<div>
		<label>Name: </label> {{customer.name}}
	</div>
	<div>
		<label>Age: </label> {{customer.age}}
	</div>
	<div>
		<label>Active: </label> {{customer.active}}
	</div>

	<span class="button is-small btn-primary" *ngIf='customer.active' (click)='updateActive(false)'>Inactive</span>

	<span class="button is-small btn-primary" *ngIf='!customer.active' (click)='updateActive(true)'>Active</span>

	<span class="button is-small btn-danger" (click)='deleteCustomer()'>Delete</span>

	<hr/>
</div>
2.6.2 CustomersListComponent

customers-list.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';

import { CustomerService } from '../customer.service';
import { Customer } from '../customer';

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

  customers: Observable<Customer[]>;

  constructor(private customerService: CustomerService, private router: Router) { }

  ngOnInit() {
    this.reloadData();
  }

  deleteCustomers() {
    this.customerService.deleteAll()
      .subscribe(
        data => {
          console.log(data);
          this.navigateToAdd();
        },
        error => console.log('ERROR: ' + error)
      );
  }

  reloadData() {
    this.customers = this.customerService.getCustomersList();
  }

  navigateToAdd() {
    this.router.navigate(['add']);
  }
}

customers-list.component.html


<br/>
<div *ngFor="let customer of customers | async" style="width: 300px;">
	<customer-details [customer]='customer'></customer-details>
</div>

<div>
	<button type="button" class="button btn-danger" (click)='deleteCustomers()'>Delete All</button>
</div>
2.6.3 CreateCustomerComponent

create-customer.component.ts


import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { Customer } from '../customer';
import { CustomerService } from '../customer.service';

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

  customer: Customer = new Customer();
  submitted = false;

  constructor(private customerService: CustomerService) { }

  ngOnInit() {
  }

  newCustomer(): void {
    this.submitted = false;
    this.customer = new Customer();
  }

  save() {
    this.customerService.createCustomer(this.customer)
      .subscribe(data => console.log(data), error => console.log(error));
    this.customer = new Customer();
  }

  onSubmit() {
    this.submitted = true;
    this.save();
  }
}

create-customer.component.html

<h3>Create Customer</h3>
<div [hidden]="submitted" style="width: 300px;">
	<form (ngSubmit)="onSubmit()">
		<div class="form-group">
			<label for="name">Name</label> <input type="text"
				class="form-control" id="name" required [(ngModel)]="customer.name"
				name="name">
		</div>

		<div class="form-group">
			<label for="age">Age</label> <input type="text"
				class="form-control" id="age" required [(ngModel)]="customer.age"
				name="age">
		</div>

		<button type="submit" class="btn btn-success">Submit</button>
	</form>
</div>

<div [hidden]="!submitted">
	<h4>You submitted successfully!</h4>
	<button class="btn btn-success" (click)="newCustomer()">Add</button>
</div>
2.6.4 SearchCustomersComponent

search-customers.component.ts

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';

import { CustomerService } from '../customer.service';
import { Customer } from '../customer';

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

  customers: Observable<Customer[]>;
  name: string;

  constructor(private customerService: CustomerService) { }

  ngOnInit() {
    this.name = '';
  }

  search() {
    this.customers = this.customerService.findCustomers(this.name);
  }
}

search-customers.component.html

<h3>Find Customers By Name</h3>
<input type="text" [(ngModel)]="name" placeholder="enter name" class="input">
 
<button class="btn btn-success" (click)="search()">Search</button>
<hr />
<ul>
	<li *ngFor="let customer of customers | async">
		<h5>{{customer.name}} - Age: {{customer.age}} - Active:	{{customer.active}}</h5>
	</li>
</ul>

2.7 AppRoutingModule

app-routing.module.ts


import { CreateCustomerComponent } from './customers/create-customer/create-customer.component';
import { CustomersListComponent } from './customers/customers-list/customers-list.component';
import { SearchCustomersComponent } from './customers/search-customers/search-customers.component';

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

const routes: Routes = [
  { path: '', redirectTo: 'customers', pathMatch: 'full' },
  { path: 'customers', component: CustomersListComponent },
  { path: 'add', component: CreateCustomerComponent },
  { path: 'search', component: SearchCustomersComponent },
];

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

export class AppRoutingModule { }

2.8 App Component

app.component.ts


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

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

export class AppComponent {
  title = 'JavaSampleApproach';
  description = 'Angular4-MongoDB';

  constructor() { }

}

app.component.html

<div class="container-fluid">
	<div style="color: blue;">
		<h1>{{title}}</h1>
		<h3>{{description}}</h3>
	</div>

	<nav>
		<a routerLink="customers" class="btn btn-primary active" role="button" routerLinkActive="active">Customers</a>
		<a routerLink="add" class="btn btn-primary active" role="button" routerLinkActive="active">Add</a>
		<a routerLink="search" class="btn btn-primary active" role="button" routerLinkActive="active">Search</a>
	</nav>
	<router-outlet></router-outlet>
</div>

3. Run & Check Result

Build and Run Spring Boot project with commandlines: mvn clean install and mvn spring-boot:run.
– Run the Angular App with command: npm start.

– Open browser with url http://localhost:4200/, add some Customers.
– Click on Customers tab, each Customer displays one after another with 1s delayed time.

angular-4-spring-webflux-reactive-mongodb-result

– Click on Search tab, search ‘Jack’, the result shows each Customer one after another with 1s delayed time.

angular-4-spring-webflux-reactive-mongodb-result-search

IV. Source Code

SpringDataReactiveMongoDB
ReactiveAngularMongoDB

49 thoughts on “Angular 10 Spring WebFlux CRUD RestAPI”

  1. I absolutely love your blog and find almost
    all of your post’s to be precisely what I’m looking for.

    Do you offer guest writers to write content in your case?
    I wouldn’t mind publishing a post or elaborating on many of the subjects you write
    about here. Again, awesome site!

  2. 594602 685842Id ought to seek advice from you here. Which is not something I do! I really like reading an post that could make folks feel. Also, several thanks allowing me to comment! 876302

  3. My developer is trying to persuade me to move to .net from PHP.
    I have always disliked the idea because of the expenses.
    But he’s tryiong none the less. I’ve been using Movable-type on various websites for about a year and am concerned about switching to another platform.
    I have heard excellent things about blogengine.net. Is
    there a way I can import all my wordpress content into it?
    Any kind of help would be really appreciated!

  4. I have not checked in here for a while as I thought it was getting boring, but the last few posts are great quality so I guess I will add you back to my daily bloglist. You deserve it my friend 🙂

  5. I think that is one of the so much important info for me.
    And i am satisfied reading your article. However want to observation on some general things, The web site
    taste is perfect, the articles is in point of fact nice
    : D. Good process, cheers

  6. naturally like your website however you need to test the spelling on several of your posts. Many of them are rife with spelling issues and I in finding it very troublesome to inform the truth on the other hand I’ll surely come back again.

  7. Perfectly written content, thanks for selective information. “No human thing is of serious importance.” by Plato.

  8. certainly like your website however you need to take a look at the spelling on quite a few of your posts. Many of them are rife with spelling issues and I in finding it very troublesome to inform the truth however I’ll definitely come again again.

  9. My partner and I stumbled over here by a different website and thought I should
    check things out. I like what I see so now i’m following
    you. Look forward to going over your web page again.

  10. I just could not depart your web site prior to suggesting that I actually enjoyed the standard info an individual supply on your guests? Is going to be again regularly in order to check out new posts.

  11. Thanks, I have just been looking for info about this topic for a while and yours is the greatest I have found out till now. But, what about the conclusion? Are you positive in regards to the supply?

  12. hi!,I like your writing so much! percentage we be in contact more about your article on AOL? I need an expert on this area to solve my problem. May be that’s you! Taking a look ahead to peer you.

  13. Wow! This could be one particular of the most useful blogs We have ever arrive across on this subject. Basically Magnificent. I am also an expert in this topic therefore I can understand your hard work.

  14. Thanks for the sensible critique. Me & my neighbor were just preparing to do some research about this. We got a grab a book from our local library but I think I learned more from this post. I’m very glad to see such fantastic information being shared freely out there.

  15. Wow! This could be one particular of the most beneficial blogs We’ve ever arrive across on this subject. Actually Fantastic. I’m also an expert in this topic so I can understand your effort.

  16. I have recently started a blog, the information you offer on this site has helped me greatly. Thank you for all of your time & work. “The very ink with which history is written is merely fluid prejudice.” by Mark Twain.

  17. I do agree with all the ideas you have offered in your post. They’re very convincing and can definitely work. Nonetheless, the posts are very short for starters. Could you please prolong them a bit from next time? Thank you for the post.

  18. Good day very nice blog!! Man .. Excellent .. Superb .. I will bookmark your website and take the feeds additionally…I am satisfied to seek out a lot of helpful info right here within the submit, we’d like work out extra strategies on this regard, thanks for sharing.

  19. Great – I should definitely pronounce, impressed with your web site. I had no trouble navigating through all the tabs as well as related info ended up being truly easy to do to access. I recently found what I hoped for before you know it at all. Quite unusual. Is likely to appreciate it for those who add forums or something, website theme . a tones way for your client to communicate. Nice task.

  20. Good day I am so grateful I found your web site, I really
    found you by error, while I was looking on Bing for something else, Anyhow I am here now and would just like
    to say thank you for a tremendous post and a
    all round exciting blog (I also love the theme/design),
    I don’t have time to look over it all at the moment
    but I have book-marked it and also included your RSS feeds, so
    when I have time I will be back to read a great deal more, Please do keep up the excellent work.

  21. I like what you guys are up also. Such smart work and reporting! Carry on the excellent works guys I have incorporated you guys to my blogroll. I think it’ll improve the value of my web site :).

  22. Howdy are using WordPress for your site platform?
    I’m new to the blog world but I’m trying to get started
    and set up my own. Do you require any html coding knowledge to make your own blog?
    Any help would be greatly appreciated!

  23. I do consider all the ideas you have offered for your post. They are very convincing and will definitely work. Nonetheless, the posts are very quick for starters. Could you please prolong them a bit from next time? Thank you for the post.

  24. What i do not realize is in truth how you’re not really much more neatly-liked than you might be now. You are so intelligent. You recognize therefore considerably on the subject of this subject, made me personally believe it from so many varied angles. Its like women and men don’t seem to be fascinated until it is one thing to accomplish with Woman gaga! Your personal stuffs nice. All the time take care of it up!

Leave a Reply

Your email address will not be published. Required fields are marked *