switch final emitted action from effect based on payload - websocket

I have an app that uses ngrx
Once a client updates a product, it uses a websocket to update all clients.
This works, by subscribing to the socket, so after a next method is called on the socket, it calls an action that handles the side effects of updating
But, now when it comes to deleting and adding, I'd like to use the same socket effect but change its final action call
Or if someone can suggest a better way
Socket service:
export class SocketService {
socket$ = Observable.webSocket( 'ws://localhost:1234');
}
effects:
//This is called from component to start the update process
#Effect({dispatch:false}) beginUpdate$: Observable<any> = this.actions$
.ofType<fromBlogActions.BlogUpdateStartAction>(fromBlogActions.BLOG_UPDATE_START_ACTION)
.map((action:any)=>{
console.log(action)
return action.payload;
})
.do((action)=> this.socketService.socket$.next(JSON.stringify(action)))
//Calls the next method to send data to the websocket
//The below watches for data emitted from the websocket
//Then calls the BlogUpdatedAction, what I need is for it to call a different action based on action type
#Effect() watchSocket$ = this.socketService.socket$
.map((action:BlogPayLoad)=>{
console.log(action)
return action
})
.mergeMap((action)=> [new fromBlogActions.BlogUpdatedAction(action)])

It should be possible like this:
#Effect() watchSocket$ = this.socketService.socket$
.map((action:BlogPayLoad)=>{
console.log(action)
return action
})
.mergeMap((action)=> {
if(action.type === 'BlogAddAction'){
return new fromBlogActions.BlogAddAction(action))
else if (...) {
....
}
else if (action.type === 'BlogUpdatedAction'){
return new fromBlogActions.BlogUpdatedAction(action))
})

Related

Angular 9 and rxjs - wait for message event after postMessage

I am new to rxjs and not sure how to implement the follow logic. Any suggestion will be appreciated.
Background
I am going to implement the communication between host website and an iframe in it with postMessage. Since postMessage is one-way only, I would like to implement the logic to wait for 'response' by myself when a message is sent from host website to iframe.
I have a sync function called send(message) to invoke the postMessage to send message to iframe. Then I would like to have another function with the follow logic.
public async sendAndWait(message): Promise<responseObj> {
// 1. create an observable to wait to message event with timeout
// my first thought is as follow but I feel like it does not work
// fromEvent(window, 'message')
// .pipe(timeout(timeoutInMs))
// .subscribe(event => {
// console.info(event);
// });
// 2. run `send(message)` function
// 3. do not finish this function until timeout or receive event in the previous subscription.
}
When I use the function, I would like to have
let response = await sendAndWait(message);
Not sure if it is possible to implement? Thank you
You cannot stop code execution in JS (using Async-Await, a Promise object is returned behind the scenes. so that the code is never waiting)
Consider implementing it in the following way:
let response: responseObj;
function main(): void {
sendAndWait(MESSAGE_OBJECT).subscribe(x => response = x)
}
function sendAndWait(message): Observable<responseObj> {
send(message)
return fromEvent(window, 'message')
.pipe(
timeout(timeoutInMs),
first()
)
}
Or optionally returning Promise:
async function sendAndWait(message): Promise<void> {
send(message)
const response = await fromEvent(window, 'message')
.pipe(
timeout(timeoutInMs),
first(),
toPromise()
)
}

How to handle long async operations in Next.js to avoid slow page loading?

When using next-redux-wrapper how do I start a long asynchronous task so that it only runs on the client? I don’t want to use await on the server side since it would delay the initial page load. I’d rather set a loading flag as the task starts and show a loading indicator until it completes.
Let’s say my async operation looks like this:
function async takesLong(store) {
store.dispatch({type: “LOADING”, payload: true});
const result = await longOperation();
store.dispatch({type: “SETDATA”}, payload: data);
return store.dispatch({type: “LOADING”, payload: false});
}
I can call this in my Next page’s getInitialProps function like this:
MyPage.getInitialProps = async ({ store, isServer }) => {
const loader = takesLong(store)
if (isServer) await loader; // <-- will delay client response
return {
someprop: "some value"
};
};
This works well if the page loads on the client side. The operation starts, and my page can display a loading-spinner until the operation completes. But when started on the server side I have a long delay before the page displays at all. I’ve tried a number of approaches but can’t find one that works properly:
Starting the process on the server and not using await renders the page without the results being written to the store, so it has only set “loading” to true in the store and it never gets the data.
Passing store in my props doesn’t work - it ends up being an empty object ({ }) in the client.
Trying to run it inside my component doesn’t seem to work for a few reasons:
a) I don’t have the store object accessible in the React Component (only in getInitialProps which won’t get called on the client).
b) Even if I use actions instead of store.dispatch in the Component, when can I call it safely? I can’t do it during render since it will change the Redux state, and componentWillReceiveProps won’t get called before the first client-side render
Is there a well defined pattern for deferring a long operation to the client-side when using Next?
Do your long async task on componentDidMount, it will run only on client side.
React in SSR not runs componentDidMount lifecycle hook.
Using bound actions during componentDidMount works. Thanks to #eenagy for the suggestion. Doing things in this order seems to do what is needed:
import { bindActionCreators } from "redux";
import { setLoading, setError, setData } from "../actions";
componentDidMount() {
if (!this.props.haveData && !this.props.loading && !this.props.error) {
this.props.setLoading(true);
loadSomeData() // <-- this takes a while to complete
.then( data => {
this.props.setData(data);
this.props.setLoading(false);
})
.catch( err => {
this.props.setError(err);
this.props.setLoading(false);
});
}
}
render() {
if (this.props.loading) return (<Loading/>);
return (/*regular page*/);
}
export const mapDispatchToProps = dispatch => {
return bindActionCreators({ setLoading, setError, setData }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(Component);
This way if the initial data is not already loaded (say by another page) it will get kicked off when the
component mounts and run asynchronously until the operation completes and
calls the actions in redux to cause the page to re-render.

Observable - Getting the value of the latest emission

I have a form and I allow the user to click as many times as he wants on a refresh button. Of course, I use debounceTime operator but I don't know how to:
either cancel the previous http requests
or indicate to my service to return the value of the latest emission.
For example:
t1: click => received data in 2000ms
t2: click => received data in 200ms
Therefore, I will get the data from t1 moment whereas the latest one is at t2.
I've tried with pipe(last()), switchMap but I don't return data.
My component:
this.filtersForm.valueChanges.pipe(debounceTime(500)).subscribe(
form => {
this.service.setFilters(form); // Set private field in service (1)
this.onSubmit();
}
);
onSubmit() {
if (this.filtersForm.valid) {
this.service.notifFiltersHasChanged();
}
}
Service:
ctor(...) {
this.filters$.subscribe(f => this.getData());
}
notifFiltersHasChanged() {
this.filters$.next(this._filters); // (1) _filters is set by setFilters method
}
getData(): void {
// ...
this.backEndService.getAll(this._filters).subscribe(data => this._data = data);
}
BackEndService:
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
The main trick is to use a single subscription (or even zero, if you'll use | async pipe in your template). So you source from an Observable and chain through your services.
Heres an updated example of yours:
Component
onDestroy$ = new Subject<void>();
constructor(){
this.filtersForm.valueChanges.pipe(
// accept only valid values
filter(() => this.filtersForm.valid),
// debounce them
debounceTime(500),
// when a value comes in -- we switch to service request
// subsequent values would cancel this request
switchMap(formValues => this.service.getData(formValues)),
// this is needed to unsubscribe from the service
// when component is destroyed
takeUntil(this.onDestroy$)
)
.subscribe(data=>{
// do what you need with the data
})
}
ngOnDestroy() {
this.onDestroy$.next(void 0);
}
Service
// service becomes stateless
// its only responsible for parsing and passing data
getData(filters): Observable<Data> {
return this.backEndService.getAll(filters);
}
BackEndService
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
Another way would be to have a Subject, that you would push to. Otherwise it would be the same chaining on top of that Subject.
Hope this helps

rxjs operator to define logic after subscribe call

const source = Rx.Observable.of(1);
const example = source
.do(val => console.log('do called'));
example.subscribe(val => console.log('subscribe called'));
//Output :
do called
subscribe called
This exemple shows that do is executed before subscribe.
Which operator do I need to use to define logic after subscribe is executed ?
I need this to define logic one time and that must be executed after each subscribe call that helps also to respect SRP (Single responsibility Principle) an example is to handle caching logic in interceptor using some kind of specific operator that I am looking for and subscribe in services
The way I handle an Interceptor is as follows, it may help if I understand your requirements correctly.
...
private interceptor(observable: Observable<Response>): Observable<Response> {
return observable
.map(res => {
return res;
})
.catch((err) => {
//handle Specific Error
return Observable.throw(err);
})
.finally(() => {
//After the request;
console.info("After the Request")
});
}
protected get(req: getHttpParams): Observable<Response> {
return this.interceptor(this.httpClient.get(`${path}/${String(req.id)}`, req.options));
}
...
I would also recommend taking a look at Angular 5's in-built interceptor for http requests specifically

RxJS: Auto (dis)connect on (un)subscribe with Websockets and Stomp

I'm building a litte RxJS Wrapper for Stomp over Websockets, which already works.
But now I had the idea of a really cool feature, that may (hopefully - correct me if I'm wrong) be easily done using RxJS.
Current behavior:
myStompWrapper.configure("/stomp_endpoint");
myStompWrapper.connect(); // onSuccess: set state to CONNECTED
// state (Observable) can be DISCONNECTED or CONNECTED
var subscription = myStompWrapper.getState()
.filter(state => state == "CONNECTED")
.flatMap(myStompWrapper.subscribeDestination("/foo"))
.subscribe(msg => console.log(msg));
// ... and some time later:
subscription.unsubscribe(); // calls 'unsubscribe' for this stomp destination
myStompWrapper.disconnect(); // disconnects the stomp websocket connection
As you can see, I must wait for state == "CONNECTED" in order to subscribe to subscribeDestination(..). Else I'd get an Error from the Stomp Library.
The new behavior:
The next implementation should make things easier for the user. Here's what I imagine:
myStompWrapper.configure("/stomp_endpoint");
var subscription = myStompWrapper.subscribeDestination("/foo")
.subscribe(msg => console.log(msg));
// ... and some time later:
subscription.unsubscribe();
How it should work internally:
configure can only be called while DISCONNECTED
when subscribeDestination is called, there are 2 possibilities:
if CONNECTED: just subscribe to the destination
if DISCONNECTED: first call connect(), then subscribe to the destination
when unsubscribe is called, there are 2 possibilities:
if this was the last subscription: call disconnect()
if this wasn't the last subscription: do nothing
I'm not yet sure how to get there, but that's why I ask this question here ;-)
Thanks in advance!
EDIT: more code, examples and explanations
When configure() is called while not disconnected it should throw an Error. But that's not a big deal.
stompClient.connect(..) is non-blocking. It has an onSuccess callback:
public connect() {
stompClient.connect({}, this.onSuccess, this.errorHandler);
}
public onSuccess = () => {
this.state.next(State.CONNECTED);
}
observeDestination(..) subscribes to a Stomp Message Channel (= destination) and returns an Rx.Observable which then can be used to unsubscribe from this Stomp Message Channel:
public observeDestination(destination: string) {
return this.state
.filter(state => state == State.CONNECTED)
.flatMap(_ => Rx.Observable.create(observer => {
let stompSubscription = this.client.subscribe(
destination,
message => observer.next(message),
{}
);
return () => {
stompSubscription.unsubscribe();
}
}));
}
It can be used like this:
myStompWrapper.configure("/stomp_endpoint");
myStompWrapper.connect();
myStompWrapper.observeDestination("/foo")
.subscribe(..);
myStompWrapper.observeDestination("/bar")
.subscribe(..);
Now I'd like to get rid of myStompWrapper.connect(). The code should automatically call this.connect() when the first one subscribes by calling observeDestination(..).subscribe(..) and it should call this.disconnect() when the last one called unsubscribe().
Example:
myStompWrapper.configure("/stomp_endpoint");
let subscription1 = myStompWrapper.observeDestination("/foo")
.subscribe(..); // execute connect(), because this
// is the first subscription
let subscription2 = myStompWrapper.observeDestination("/bar")
.subscribe(..);
subscription2.unsubscribe();
subscription1.unsubscribe(); // execute disconnect(), because this
// was the last subscription
RxJS: Auto (dis)connect on (un)subscribe with Websockets and Stomp
I agree the code you are suggesting to tuck away into myStompWrapper will be happier in its new home.
I would still suggest to use a name like observeDestination rather than subscribeDestination("/foo") as you are not actually subscribing from that method but rather just completing your observable chain.
configure() can only be called while DISCONNECTED
You do not specify here what should happen if it is called while not DISCONNECTED. As you do not seem to be returning any value here that you would use, I will assume that you intend to throw an exception if it has an inconvenient status. To keep track of such statuses, I would use a BehaviourSubject that starts with the initial value of DISCONNECTED. You likely will want to keep state within observeDestination to decide whether to throw an exception though
if CONNECTED: just subscribe to the destination
if DISCONNECTED: first call connect(), then subscribe to the destination
As I mentioned before, I think you will be happier if the subscription does not happen within subscribeDestination("/foo") but rather that you just build your observable chain. As you simply want to call connect() in some cases, I would simply use a .do() call within your observable chain that contains a condition on the state.
To make use of the rx-y logic, you likely want to call disconnect() as part of your observable unsubscribe and simply return a shared refcounted observable to start with. This way, each new subscriber does not recreate a new subscription, instead .refCount() will make a single subscription to the observable chain and unsubscribe() once there is no more subscribers downstream.
Assuming the messages are coming in as this.observedData$ in myStompWrapper My suggested code as part of myStompWrapper would look something like this:
observeDestination() {
return Rx.Observable.create(function (observer) {
var subscription = this.getState()
.filter(state => state == "CONNECTED")
.do(state => state ? this.connect() : Observable.of(true))
.switchMap(this.observedData$)
.refCount();
.subscribe(value => {
try {
subscriber.next(someCallback(value));
} catch(err) {
subscriber.error(err);
}
},
err => subscriber.error(err),
() => subscriber.complete());
return { unsubscribe() { this.disconnect(); subscription.unsubscribe(); } };
}
Because I am missing some of your code, I am allowing myself to not test my code. But hopefully it illustrates and presents the concepts I mentioned in my answer.

Resources