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 4
– How to use Angular HttpClient to POST, PUT, DELETE data on SpringBoot Rest APIs – Angular 4
– How to build SpringBoot MongoDb RestfulApi
– How to use SpringData MongoRepository to interact with MongoDB
– Angular 4 + 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 4
– RxJS 5.1.0
– MongoDB 3.4.10
II. Overview
1. Full Stack 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 "jsa_mongodb";
}
}
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(...)
).
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
– 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
2.2 Project 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.
– Click on Search tab, search ‘Jack’, the result shows each Customer one after another with 1s delayed time.
IV. Source Code
– SpringDataReactiveMongoDB
– ReactiveAngularMongoDB
why do you write
`${this.baseUrl}`
when you could just write
this.baseUrl
Is it possible to pass event headers like bearer token?
323854 297888Hey there guys, newbie here. Ive lurked about here for slightly even though and thought Id take part in! Looks like youve got quite a great place here 743502
40477 306838certainly like your web-site but you have to test the spelling on several of your posts. 50788
Lady Gaga taught me its ok to be different.. . Ke$ha taught me to be myself and not care? what? anyone else thinks. . . Bruno? Mars taught me?? to do anything for that one? person? I love. Eminem taught? me? that life is hard but you can make it through. ? Taylor? Swift taught me not every guy is going to treat me right.. . ? Michael Jackson taught me to always love the people around me.. . Music taught? me how? to live. BUT: most importantly,? Rebecca Black taught me the days of the week.
When I originally commented I clicked the -Notify me when new comments are added- checkbox and today each time a comment is added I recieve four emails with the same comment. Is there that is it is possible to remove me from that service? Thanks!
Nice post. I was checking constantly this blog and I am impressed! Very useful info specially the last part I care for such information a lot. I was looking for this particular info for a very long time. Thank you and best of luck.
The luxurious introduced is literally un; rrndividuals are automatically longing for many of the Hawaiian ideal. This skill unusual vacation spot is intended immediately to grow variation and simply dominance throughout tourist business enterprise. hotels promotions
174378 157158I saw a great deal of website but I believe this one has got something unique in it in it 21602
I discovered your blog site internet site on the search engines and appearance a few of your early posts. Keep on the very good operate. I recently extra the Rss to my MSN News Reader. Looking for toward reading more by you at a later time!…
It’s exhausting to find educated people on this topic, however you sound like you recognize what you’re speaking about! Thanks
This could be the ideal blog page for anybody who wants to learn about this theme. You recognize so much its nearly hard to argue with you (not that I really would want…HaHa). You definitely set a new spin on the subject matter thats been written about for years. Wonderful things, just wonderful!
I’m not sure where you’re getting your info, but great topic. I needs to spend some time learning much more or understanding more. Thanks for excellent information I was looking for this information for my mission. xrumer
Good day! Do you use Twitter? I’d like to follow you if that would be ok. I’m absolutely enjoying your blog and look forward to new updates.
Undetected Game Hacks
237104 613708Keep all the articles coming. I adore reading by way of your items. Cheers. 209676
748606 792460I certainly did not recognize that. Learnt 1 thing new today! Thanks for that. 19982
113838 167272Have you noticed the news has changed its approach recently? What used to neve be brought up or discussed has changed. It is that time to chagnge our stance on this though. 831119