Angular + Spring WebFlux + Spring Data Reactive Cassandra example | Full-Reactive Angular Http Client – Spring Boot RestApi Server

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

Related Posts:
SpringData Reactive Cassandra Repositories | SpringBoot
Introduction to RxJS – Extensions for JavaScript Reactive Streams
Angular 4 + Spring Boot + Cassandra CRUD example

I. Technologies

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite 3.9.0.RELEASE
– Spring Boot 2.0.0.RELEASE
– Angular 5
– RxJS 5.1.0
– Cassandra 3.9.0

II. Overview

1. Full Stack Architecture

angular-spring-webflux-spring-data-cassandra-reactive-architecture

2. Reactive Spring Boot Server

2.1 Dependency

	org.springframework.boot
	spring-boot-starter-data-cassandra-reactive



	org.springframework.boot
	spring-boot-starter-webflux

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{
	Flux findByLastname(String lastname);

	@Query("SELECT * FROM customer WHERE firstname = ?0 and lastname  = ?1")
	Mono findByFirstnameAndLastname(String firstname, String lastname);

	// for deferred execution
	Flux findByLastname(Mono lastname);

	Mono findByFirstnameAndLastname(Mono firstname, String lastname);
}
2.3 Activate reactive Spring Data Cassandra

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

import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.AbstractReactiveCassandraConfiguration;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.repository.config.EnableReactiveCassandraRepositories;

@Configuration
@EnableReactiveCassandraRepositories
public class CassandraReactiveConfig extends AbstractReactiveCassandraConfiguration {

	@Override
	protected String getKeyspaceName() {
		return "javasampleapproach";
	}

	@Override
	public SchemaAction getSchemaAction() {
		return SchemaAction.RECREATE;
	}
}
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 java.util.UUID;
import com.datastax.driver.core.utils.UUIDs;
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 getAllCustomers() {
		return customerRepository.findAll();
	}

	@PostMapping("/customers/create")
	public Mono createCustomer(@Valid @RequestBody Customer customer) {
		customer.setId(UUIDs.timeBased());
		customer.setActive(false);
		return customerRepository.save(customer);
	}

	@PutMapping("/customers/{id}")
	public Mono> updateCustomer(@PathVariable("id") UUID 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 deleteCustomer(@PathVariable("id") UUID 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() {
		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/findbyage")
	public Flux findByAge(@RequestParam int age) {
		return customerRepository.findByAge(age);
	}
}

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 extends Repository {

	 Mono save(S entity);
	Mono findById(ID id);
	Flux findAll();
	Mono deleteById(ID id);
	Mono deleteAll();
	// ...
}

And findByAge method that we create in our interface ReactiveCustomerRepository:

public interface ReactiveCustomerRepository extends ReactiveCrudRepository{
	Flux findByAge(int age);
}

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, NgZone } 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, private zone: NgZone) {
  }

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

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

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

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

    return Observable.create((observer) => {
      const eventSource = new EventSource(`${this.baseUrl}`);

      eventSource.onmessage = (event) =>
        this.zone.run(() => {
          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 {
    return this.http.delete(`${this.baseUrl}` + `/delete`, { responseType: 'text' });
  }

  findCustomers(age): Observable {
    this.customersListSearch = new Array();

    return Observable.create((observer) => {
      const eventSource = new EventSource(`${this.baseUrl}` + `/findbyage?age=` + age);

      eventSource.onmessage = (event) =>
        this.zone.run(() => {
          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(...)).

We have to instantiate a NgZone and call zone.run(callback) to help Angular notify when change happens.

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;

  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:

III. Practice

0. Set up Cassandra

Open Cassandra CQL Shell:

– Create Cassandra keyspace with name javasampleapproach:

create keyspace javasampleapproach with replication={'class':'SimpleStrategy', 'replication_factor':1};

– Create customer table for javasampleapproach keyspace:

use javasampleapproach;

CREATE TABLE customer(
   id timeuuid PRIMARY KEY,
   name text,
   age int,
   active boolean
);

– Because we use repository finder method on age field, so we need to create an index on age column:

CREATE INDEX ON javasampleapproach.customer (age);

1. Reactive Spring Boot Server

1.1 Project Structure

angular-spring-webflux-spring-data-cassandra-reactive-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.
– Dependencies for Spring Boot WebFlux and Spring Data Cassandra in pom.xml.

1.2 Dependency

	org.springframework.boot
	spring-boot-starter-data-cassandra-reactive



	org.springframework.boot
	spring-boot-starter-webflux

1.3 Data Model
package com.javasampleapproach.reactive.cassandra.model;

import java.util.UUID;

import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;

@Table
public class Customer {
 
	@PrimaryKey
	private UUID 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 UUID getId() {
		return id;
	}
 
	public void setId(UUID id) {
		this.id = 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.cassandra.repo;

import java.util.UUID;

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

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

import reactor.core.publisher.Flux;

public interface ReactiveCustomerRepository extends ReactiveCrudRepository{
	Flux findByAge(int age);
}
1.5 Enable reactive Spring Data Cassandra
package com.javasampleapproach.reactive.cassandra.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.AbstractReactiveCassandraConfiguration;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.repository.config.EnableReactiveCassandraRepositories;

@Configuration
@EnableReactiveCassandraRepositories
public class CassandraReactiveConfig extends AbstractReactiveCassandraConfiguration {

	@Override
	protected String getKeyspaceName() {
		return "javasampleapproach";
	}

	@Override
	public SchemaAction getSchemaAction() {
		return SchemaAction.RECREATE;
	}

}
1.6 REST Controller
package com.javasampleapproach.reactive.cassandra.controller;

import java.time.Duration;
import java.util.UUID;

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.cassandra.model.Customer;
import com.javasampleapproach.reactive.cassandra.repo.ReactiveCustomerRepository;

import com.datastax.driver.core.utils.UUIDs;
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.setId(UUIDs.timeBased());
		customer.setActive(false);
		return customerRepository.save(customer);
	}

	@PutMapping("/customers/{id}")
	public Mono> updateCustomer(@PathVariable("id") UUID 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") UUID 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/findbyage")
	public Flux findByAge(@RequestParam int age) {

		return customerRepository.findByAge(age).delayElements(Duration.ofMillis(1000));
	}
}

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

2. Reactive Angular Client

2.1 User Interface

angular-spring-webflux-spring-data-cassandra-reactive-client-ui

2.2 Project Structure

angular-spring-webflux-spring-data-cassandra-reactive-client-structure

In this example, we have:
– 4 components: customers-list, customer-details, create-customer, search-customers.
– 4 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 { CreateCustomerComponent } from './customers/create-customer/create-customer.component';
import { CustomerDetailsComponent } from './customers/customer-details/customer-details.component';
import { CustomersListComponent } from './customers/customers-list/customers-list.component';
import { SearchCustomersComponent } from './customers/search-customers/search-customers.component';

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

@NgModule({
  declarations: [
    AppComponent,
    CreateCustomerComponent,
    CustomerDetailsComponent,
    CustomersListComponent,
    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, NgZone } 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, private zone: NgZone) {
  }

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

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

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

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

    return Observable.create((observer) => {
      const eventSource = new EventSource(`${this.baseUrl}`);

      eventSource.onmessage = (event) =>
        this.zone.run(() => {
          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 {
    return this.http.delete(`${this.baseUrl}` + `/delete`, { responseType: 'text' });
  }

  findCustomers(age): Observable {
    this.customersListSearch = new Array();

    return Observable.create((observer) => {
      const eventSource = new EventSource(`${this.baseUrl}` + `/findbyage?age=` + age);

      eventSource.onmessage = (event) =>
        this.zone.run(() => {
          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

{{customer.name}}
{{customer.age}}
{{customer.active}}
Inactive Active Delete
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;

  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


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

Create Customer

You submitted successfully!

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;
  age: number;

  constructor(private customerService: CustomerService) { }

  ngOnInit() {
    this.age = 0;
  }

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

}

search-customers.component.html

Find Customers By Age


  • {{customer.name}} - Age: {{customer.age}} - Active: {{customer.active}}
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 = 'Reactive-Angular-Cassandra';

  constructor() { }
}

app.component.html

{{title}}

{{description}}

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-spring-webflux-spring-data-cassandra-reactive-client-result

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

angular-spring-webflux-spring-data-cassandra-reactive-client-result-search

IV. Source Code

SpringDataReactiveCassandra
ReactiveAngularCassandra



By grokonez | March 17, 2018.


Related Posts


1 thought on “Angular + Spring WebFlux + Spring Data Reactive Cassandra example | Full-Reactive Angular Http Client – Spring Boot RestApi Server”

Got Something To Say:

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

*