Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular client of Spring Boot 2 Reactor Flux API

How do I create an Angular 4 client for a Java Project Reactor reactive Flux API? The sample below has two APIs: a Mono API; and, Flux API. Both work from curl; but in Angular 4 (4.1.2) only the Mono API works; any ideas how to get Angular 4 to work with the Flux API?

Here's a trivial Spring Boot 2.0.0-SNAPSHOT application with a Mono API and a Flux API:

@SpringBootApplication
@RestController
public class ReactiveServiceApplication {

    @CrossOrigin
    @GetMapping("/events/{id}")
    public Mono<Event> eventById(@PathVariable long id) {
        return Mono.just(new Event(id, LocalDate.now()));
    }

    @CrossOrigin
    @GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<Event> events() {
        Flux<Event> eventFlux = Flux.fromStream(
            Stream.generate(
                ()->new Event(System.currentTimeMillis(), LocalDate.now()))
            );

        Flux<Long> durationFlux = Flux.interval(Duration.ofSeconds(1));

        return Flux.zip(eventFlux, durationFlux).map(Tuple2::getT1);
    }

    public static void main(String[] args) {
        SpringApplication.run(ReactiveServiceApplication.class);
    }
}

with a Lombok-ed event:

@Data
@AllArgsConstructor
public class Event {
    private final long id;
    private final LocalDate when;
}

These reactive APIs work from curl as I'd expect:

jan@linux-6o1s:~/src> curl -s http://localhost:8080/events/123
{"id":123,"when":{"year":2017,"month":"MAY","monthValue":5,"dayOfMonth":15,"dayOfWeek":"MONDAY","era":"CE","dayOfYear":135,"leapYear":false,"chronology":{"calendarType":"iso8601","id":"ISO"}}}

and similarly for the non-terminating Flux API:

jan@linux-6o1s:~/src> curl -s http://localhost:8080/events
data:{"id":1494887783347,"when":{"year":2017,"month":"MAY","monthValue":5,"dayOfMonth":15,"dayOfWeek":"MONDAY","era":"CE","dayOfYear":135,"leapYear":false,"chronology":{"calendarType":"iso8601","id":"ISO"}}}

data:{"id":1494887784348,"when":{"year":2017,"month":"MAY","monthValue":5,"dayOfMonth":15,"dayOfWeek":"MONDAY","era":"CE","dayOfYear":135,"leapYear":false,"chronology":{"calendarType":"iso8601","id":"ISO"}}}

data:{"id":1494887785347,"when":{"year":2017,"month":"MAY","monthValue":5,"dayOfMonth":15,"dayOfWeek":"MONDAY","era":"CE","dayOfYear":135,"leapYear":false,"chronology":{"calendarType":"iso8601","id":"ISO"}}}

...

The similarly trivial Angular 4 client with RxJS:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent  implements OnInit, OnDestroy {
  title = 'app works!';
  event: Observable<Event>;
  subscription: Subscription;

  constructor(
    private _http: Http
    ) {
  }

  ngOnInit() {
    this.subscription = this._http
      .get("http://localhost:8080/events/322")
      .map(response => response.json())
      .subscribe(
        e => { 
          this.event = e;
        }
      );
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

works fine for the Mono API:

"http://localhost:8080/events/322"

but the Flux API:

"http://localhost:8080/events"

never triggers the event handler, unlike curl.

like image 983
Jan Nielsen Avatar asked May 15 '17 22:05

Jan Nielsen


People also ask

How to integrate angular with Spring Boot RESTful API?

How to Integrate Angular with Spring Boot RESTful API. 1 1. Install Node.js for Angular. Download and install Node.js from their website. If the installation is successful, you would see the following items ... 2 2. Install Angular-CLI. 3 3. Create Angular Client Project. 4 4. Import Angular Client Project. 5 5. Generate Components. More items

How do I create an angular client project in spring?

Create Angular Client Project On the command line, navigate to your IDE’s workspace by entering, CD C:\Users\User\workspace (specify your workspace path here). Start a new Angular project by entering ng new angular4-client –routing. 4. Import Angular Client Project Import Angular client project into Spring Tool Suite.

What ports does angular4-client work with Spring Boot?

? 5. Integrate Angular and Spring Boot Up to now, Angular4-Client and Spring Boot server worked independently on ports 8080 and 4200. The goal of the below integration is to ensure that client at 4200 will proxy any API requests to the server.

What is the use of Spring Boot in angular?

Spring Boot and Angular form a powerful tandem that works great for developing web applications with a minimal footprint. In this tutorial, we'll use Spring Boot for implementing a RESTful backend, and Angular for creating a JavaScript-based frontend. Learn how to create controllers using Spring MVC request annotation on Java interfaces.


1 Answers

Here's a working Angular 4 SSE example as Simon describes in his answer. This took a while to piece together so perhaps it'll be useful to others. The key piece here is Zone -- without Zone, the SSE updates won't trigger Angular's change detection.

import { Component, NgZone, OnInit, OnDestroy } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/map';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent  implements OnInit {
  event: Observable<MyEvent>;
  private _eventSource: EventSource;
  private _events: BehaviorSubject<MyEvent> = new BehaviorSubject<MyEvent>(null);
  constructor(private _http: Http, private _zone: NgZone) {}
  ngOnInit() {
    this._eventSource = this.createEventSource();
    this.event = this.createEventObservable();
  }

  private createEventObservable(): Observable<MyEvent> {
    return this._events.asObservable();
  }

  private createEventSource(): EventSource {
      const eventSource = new EventSource('http://localhost:8080/events');
      eventSource.onmessage = sse => {
        const event: MyEvent = new MyEvent(JSON.parse(sse.data));
        this._zone.run(()=>this._events.next(event));
      };
      eventSource.onerror = err => this._events.error(err);
      return eventSource;
  }
}

The corresponding HTML is simply:

<b>Observable of sse</b>
<div *ngIf="(event | async); let evt; else loading">
  <div>ID: {{evt.id}} </div>
</div>
<ng-template #loading>Waiting...</ng-template>

The event is trivial:

export class MyEvent {
  id: number;
  when: any;

  constructor(jsonData) {
    Object.assign(this, jsonData);
  }
}

and since my TS does not include EventSource or Callback, I stubbed them in:

interface Callback { (data: any): void; }

declare class EventSource {
    onmessage: Callback;
    onerror: Callback;
    addEventListener(event: string, cb: Callback): void;
    constructor(name: string);
    close: () => void;
}
like image 170
Jan Nielsen Avatar answered Sep 18 '22 00:09

Jan Nielsen