Return EventEmitter as Observable in Nest.js - rxjs

EventEmitter in Nestjs is wrapper around EventEmitter2 module. I whant that Server-Sent Events return Observable with EE.
import { Controller, Post, Body, Sse } from '#nestjs/common';
import { fromEvent } from 'rxjs';
import { EventEmitter2 } from '#nestjs/event-emitter';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';
#Controller('orders')
export class OrdersController {
constructor(private ordersService: OrdersService,
private eventEmitter2: EventEmitter2) {}
#Post()
createOrder(#Body() createOrderDto: CreateOrderDto) {
// save `Order` in Mongo
const newOrder = this.ordersService.save(createOrderDto);
// emit event with new order
this.eventEmitter2.emit('order.created', newOrder);
return newOrder;
}
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
}
But after subscribtion to this source from browser i've getting only errors
this.eventSource = new EventSource('http://localhost:3000/api/v1/orders/newOrders');
this.eventSource.addEventListener('open', (o) => {
console.log("The connection has been established.");
});
this.eventSource.addEventListener('error', (e) => {
console.log("Some erorro has happened");
console.log(e);
});
this.eventSource.addEventListener('message', (m) => {
const newOder = JSON.parse(m.data);
console.log(newOder);
});

It's quite likely that you forgot to format the event in the right way.
For SSE to work internally, each chunk needs to be a string of such format: data: <your_message>\n\n - whitespaces do matter here. See MDN reference.
With Nest.js, you don't need to create such message manually - you just need to return a JSON in the right structure.
So in your example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
would have to be adjusted to, for example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created')
.pipe(map((_) => ({ data: { newOrder } })));
}
the structure { data: { newOrder } } is key here. This will be later translated by Nest.js to earlier mentioned data: ${newOrder}\n\n

Related

How to hook up a Long running Process in NestJS with ws ( Websockets ) and RxJs

I want to run code in a function - and then return as a websocket Observable. Effectively monitoring a long running process. I can not figure out how to return the values correctly through the websockets in this format.
My long-running process: ( obviously not going to actually take a long time )
import { Observable } from 'rxjs';
export function longRunningProcess (): Observable<unknown> {
return new Observable(subscriber => {
subscriber.next('End of step 1');
subscriber.next('End of step 2');
subscriber.next('End of step 3');
setTimeout(() => {
subscriber.next('End of Step 4');
subscriber.complete();
}, 1000);
});
}
My NestJS endpoint that returns to the ws ( Websocket )
import { WsAdapter } from '#nestjs/platform-ws';
import {
MessageBody,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
WsResponse,
} from '#nestjs/websockets';
import { from, Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { Server } from 'ws';
import { longRunningProcess } from './test'
#WebSocketGateway()
export class EventsGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('events')
// send {"event":"events","data":"test"} in websockets
findAll (#MessageBody() data: any): Observable<WsResponse<unknown>> {
return from(longRunningProcess) // Not really sure how to return this
//return from([1, 2, 3]).pipe(map(item => ({ event: 'events', data: item }))); //<< this works from the sample
}
#SubscribeMessage('identity')
async identity (#MessageBody() data: number): Promise<number> {
return data;
}
}
just map your result from the longRunningProcess like you've did for the numbers array.
#SubscribeMessage('events')
findAll (#MessageBody() data: any): Observable<WsResponse<unknown>> {
return longRunningProcess().pipe(map(item => ({ event: 'events', data: item })));
}

Fetching asynchronous data in a lit-element web component

I'm learning how to fetch asynchronous data in a web component using the fetch API and lit-element:
import {LitElement, html} from 'lit-element';
class WebIndex extends LitElement {
connectedCallback() {
super.connectedCallback();
this.fetchData();
}
fetchData() {
fetch('ajax_url')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
};
response.json();
})
.then(data => {
this.data = data;
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
}
render() {
if (!this.data) {
return html`
<h4>Loading...</h4>
`;
}
return html`
<h4>Done</h4>
`;
}
}
customElements.define('web-index', WebIndex);
However the html rendered never changes. What I'm doing wrong? Is this the best way to fetch asynchronous data in a web component?
You need to register data in component properties so that the render is called once value of data is changed
static get properties() {
return {
data: Object
}
}
https://lit-element.polymer-project.org/guide/properties

Passing data between more than one component using single model and presist it?

I have this service:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DataService<T> {
private subject: BehaviorSubject<Partial<T>> = new BehaviorSubject<Partial<T>>(null);
changeData(message: Partial<T>) {
this.subject.next(message);
}
clearData() {
this.subject.next(null);
}
getData(): Observable<Partial<T>> {
return this.subject.asObservable();
}
}
I'm using it like this.
Datepicker:
this.ds.changeData({
dateFrom: this.dateRange.start,
dateTo: this.dateRange.end
});
Main Component:
this.ds.getData().subscribe((data: FilterQuery) => {
console.log('Update data', data);
this.filterModel = data;
});
I would like to add few more components what is the best way to presist the data and build full query object? If you have any other suggestions, I will gladly appreciate it.

How to use combineLatest with function that return dynamically Observable?

If you use a function that returns different observable like:
const load = () => {
if (!activated) {
return of(null);
} else {
return of(123);
}
}
And if you use combineLatest, it will always the the returned value at that moment, even if you change the activated to true.
combineLatest(load(), b)
.pipe(map(([num, str]) => `${num}:${str}`))
.subscribe(data => log(`result: ${data}`))
b.next('a');
activated = true;
b.next('b'); // should log "123:b", but it doesn't
You can check the full example here: https://stackblitz.com/edit/combinelatest-dynamically
Any solution to always get the updated version?
ps: I cannot have a single Subscription, cause it's a middleware from localStorage
You just need to make activated observable and have it feed load(). The following is modified from your stackblitz.
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { combineLatest } from 'rxjs/observable/combineLatest';
import {merge} from "rxjs/observable/merge";
import { of } from 'rxjs/observable/of';
import { defer } from 'rxjs/observable/defer';
import { map, switchMap } from 'rxjs/operators';
const activated = new BehaviorSubject<boolean>(false);
const b = new ReplaySubject<any>();
const load = (activated) => {
if (!activated) {
return of(null);
} else {
return of(123);
}
}
combineLatest(defer(() => activated.pipe(switchMap(x => load(x)))), b)
.pipe(map(([num, str]) => `${num}:${str}`))
.subscribe(data => log(`result: ${data}`))
b.next('a');
activated.next(true);
b.next('b'); // should log "123:b", but it doesn't
// Utils
function log(v) {
document.body.querySelector('pre').appendChild(document.createElement('div')).textContent = v;
}

React Apollo subscription bypasses the graphql wrapper

I have a sample app called GraphQL Bookstore that creates books, publishers and authors and shows relationships between them. I am using subscriptions to show updates in real time.
For some reason my BOOK_ADDED subscription is bypassing the graphql wrapper completely. It is calling the wrapped class with the books prop set to undefined. Relevant parts of the code are shown below (you can see the full code here).
class BooksContainerBase extends React.Component {
componentWillMount() {
const { subscribeToMore } = this.props;
subscribeToMore({
document: BOOK_ADDED,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const newBook = subscriptionData.data.bookAdded;
// Don't double add the book
if (!prev.books.find(book => book.id === newBook.id)) {
return Object.assign({}, prev, {
books: [...prev.books, newBook]
});
} else {
return prev;
}
}
});
}
render() {
const { books } = this.props;
return <BooksView books={books} />;
}
}
...
export const BooksContainer = graphql(BOOKS_QUERY, {
props: ({ data: { loading, error, subscribeToMore, books } }) => ({
loading,
error,
subscribeToMore,
books
})
})(LoadingStateViewer(BooksContainerBase));
Basically when a subscription notification is received by the client, the updateQuery() function is called - as expected. However, as soon as that function exits, the render() method of the wrapped class is called directly with books set to undefined. I expected that the graphql wrapper would be called, setting the props correctly before calling the render() method. What am I missing?
Thanks in advance!

Resources