execute extra observable/promise at the end of the http request - rxjs

I have an interceptor and I need to chain/execute a promise/observable when the request comes back from the server. How can I do this in rxjs 6?
intercept(): Observable<HttpEvent<any>> {
return next.handle(request)[DO-SOMETHING]
}

If you need to modify the http event:
return next.handle(request).pipe(
mergeMap(request => yourPromiseOrObservable));
What does mergeMap do?
Projects each source value to an Observable which is merged in the output Observable.
And for more help.
If you don't need to modify the http event:
return next.handle(request).pipe(
tap(request => yourPromise.then(_ => ;)));
What does tap do?
Perform a side effect for every emission on the source Observable, but return
an Observable that is identical to the source.
Or, if you need to wait for the promise but don't modify the request:
return next.handle(request).pipe(
delayWhen(request => from(yourPromise)));
What does delayWhen do?
Delays the emission of items from the source Observable by a given time span determined by the emissions of another Observable.

Related

Should I unsubscribe after a complete?

I have a quick question about observable.
I have the following observable:
getElevation(pos: Cartographic): Observable<Cartographic> {
return new Observable(observer => {
const promise = Cesium.sampleTerrain(this.terrainProvider, 11, Cesium.Cartographic(pos.longitude, pos.latitude))
Cesium.when(promise, (updatedPositions) => {
observer.next(updatedPositions);
observer.complete();
});
});
}
In a component I have:
this.service.getElevation(value).subscribe((e) => {});
My question is, this is a one shoot observable, so I complete just after, is the complete automatically close the subscription? or, do I also have to do this:
const sub = this.service.getElevation(value).subscribe((e) => {sub.unsubscribe();});
In your case you don't need to unsubscribe.
All Observers will automatically be unsubscribed when you call complete. That said, you may want to implement your consuming (component) code do handle the possibility that the implementation of the service may change in the future.
You could do this by using the take operator which will unsubscribe after the first value is emitted:
this.service.getElevation(value).pipe(take(1)).subscribe((e) => {});
You should not unsubscribe in a subscription, it the observable emits instantly then sub is undefined.
If you want a self unsubscribing observable you can use takeUntil
finalise = new Subject();
this.service.getElevation(value).pipe(takeUntil(finalise)).subscribe((e) => {
finalise.next();
finalise.complete();
});
Brief note:
Try to control the subscription with operators such as takeUntil.
You don’t need to unsubscribe yourself if the sender(Subject) completes.
For your case, since the sender returned by getElevation function completes itself after emitting a value one time, you don’t need to either use any operator or unsubscribe yourself to unsubscribe it.
All you have to do: this.service.getElevation(value).subscribe((v) => // do what you want);

RxJs: How to know which operators closes stream?

I am interested, is there a way to know whether the operator closes a stream or not?
I've been trying to find it in documentation, but with no luck today.
I think you are looking for "complete" callback (or third parameter) of subscribe() method [see the detailing in the comments] -
yourObservable.pipe(
take(1) //or take any number of values i.e. which is a finite number,
map(),
//or some other operators as per your requirement
).subscibe(
//first call back is to handle the emitted value
//this will be called every time a new value is emitted by observable
(value) => {
//do whatever you want to do with value
console.log(value);
},
//second callback is for handling error
//This will be called if an observable throws an exception
//once an exception occurred then also observable complete and no more values recieved by the subscriber
//Either complete callback is called or error callback is called but not
//both
(exception) => {
//do whatever you want to do with error
console.log(error);
},
//third callback will be called only when source observable is complete
//otherwise it will never get called
//This is the place to know if an observable is completed or not
//Once complete callback fires, subscription automatically unsubscribed
() => {
console.log(`Observable is complete and will not emit any new value`)
}
);
see the following stackblitz - https://stackblitz.com/edit/rxjs-toarray-xwvtgk?file=index.ts&devtoolsheight=100

deferred Rxjs BehaviorSubject mechanism

I have the following requirement.
I have An Angular service with an BehaviorSubject.
A http request is done and when this is done the BehaviorSubject.next method is invoked with the value.
This value can change during the lifecycle of the single page.
Different subscribers are registered to it and get invoked whenever this changes.
The problem is that while the http request is pending the BehaviorSubject already contains a default value and subscribers are already immediately getting this value.
What I would want is that subscribers have to wait till the http request is done (deferred) and get the value when the http request is done and sets the value.
So what I need is some kind of deferred Behavior subject mechanism.
How would i implement this using rxjs?
Another requirement is that if I subscribe to the behaviorsubject in a method we want the subcriber to get the first non default value and that the subscription ends. We don't want local subscriptions in functions to be re-executed.
Use a filter on your behavior subject so your subscribers won't get the first default emitted value:
mySubject$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
httpResponse$: Observable<any> = this.mySubject$.pipe(
filter(response => response)
map(response => {
const modifyResponse = response;
// modify response
return modifyResponse;
}),
take(1)
);
this.httpResponse$.subscribe(response => console.log(response));
this.myHttpCall().subscribe(response => this.mySubject$.next(response));
You can of course wrap the httpResponse$ observable in a method if you need to.
I think the fact that you want to defer the emitted default value, straight away brings into question why you want to use a BehaviorSubject. Let's remember: the primary reason to use a BehaviorSubject (instead of a Subject, or a plain Observable), is to emit a value immediately to any subscriber.
If you need an Observable type where you need control of the producer (via .next([value])) and/or you want multicasting of subscription out of the box, then Subject is appropriate.
If an additional requirement on top of this is that subscribers need a value immediately then you need to consider BehaviorSubject.
If you didn't say you need to update the value from other non-http events/sources, then I would've suggested using a shareReplay(1) pattern. Nevertheless...
private cookieData$: Subject<RelevantDataType> = new
Subject<RelevantDataType>(null);
// Function for triggering http request to update
// internal Subject.
// Consumers of the Subject can potentially invoke this
// themselves if they receive 'null' or no value on subscribe to subject
public loadCookieData(): Observable<RelevantDataType> {
this.http.get('http://someurl.com/api/endpoint')
.map(mapDataToRelevantDataType());
}
// Function for dealing with updating the service's
// internal cookieData$ Subject from external
// consumer which need to update this value
// via non-http events
public setCookieData(data: any): void {
const newCookieValue = this.mapToRelevantDataType(data); // <-- If necessary
this.cookieData$.next(newCookieValue); // <-- updates val for all subscribers
}
get cookieData(): Observable<RelevantDataType> {
return this.cookieData$.asObservable();
}
The solution is based on OPs comments etc.
- deals with subscribing to subject type.
- deals with external subscribers not being able to 'next' a new value directly
- deals with external producers being able to set a new value on the Subject type
- deals with not giving a default value whilst http request is pending

RxJS: Is there an no-op observable?

I have an action that will then trigger an ajax request.
If the action fails for some reason, I want to do nothing. Instead of creating a blank action that just returns the previous state, is there a no-op function I can execute?
export default function fetchMeetups(action$) {
return action$.ofType(statusActions.START_APP)
.mergeMap(action =>
ajax.getJSON(`${config.API_BASE_URL}/api/v1/meetups`)
.map(meetups => calendarActions.meetupsReceived(meetups))
)
.catch(error => Observable.noop())
};
I already have the meetups saved from the last time the app was open (using redux-persist), so if the api request fails I just want it to do nothing.
Is this possible?
I found this from Rxjs but I have no clue how to use it: https://xgrommx.github.io/rx-book/content/helpers/noop.html
Heads up: that link to xgrommx references RxJS v4, not v5 or v6. noop is also just a function that does nothing--not an Observable which emits nothing, which is what I believe you're looking for.
That said, I would highly discourage against swallowing errors completely like this. It can make debugging this and other things very very hard later. I would at least log the error message out.
v5 comes with Observable.empty() or import { empty } from 'rxjs/observable/empty'; which produces an Observable that will emit nothing and just immediately complete.
However, there are some other subtleties you probably will run into next. If you let the ajax error propagate up to the outer operator chain, outside of the mergeMap, your Epic will not longer be listening for future actions! Instead, you'll want to catch errors as early as possible, in this case by placing the catch inside the mergeMap. We often call this "isolating our observer chains"
export default function fetchMeetups(action$) {
return action$.ofType(statusActions.START_APP)
.mergeMap(action =>
ajax.getJSON(`${config.API_BASE_URL}/api/v1/meetups`)
.map(meetups => calendarActions.meetupsReceived(meetups))
.catch(e => {
console.error(e);
return Observable.empty();
})
);
};
Now, whenever the ajax (or the map operation) errors, we're catching that error before it propagates out and instead switching to our empty Observable which will complete immediately so the inner chain is now "done" but our Epic will continue to listen for future actions.
UPDATE:
In v6 empty() is imported from the root import { empty } from 'rxjs'; or it is also available as a singleton import { EMPTY } from 'rxjs';, which can be used as-is, you don't call it like you would empty(). It can be reused because Observables are lazy and act like a factory anyway so empty() was redundant.
import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
// etc
source$.pipe(
catchError(e => {
console.error(e);
return EMPTY; // it's not a function, use it as-is.
})
);
As rxjs also accepts arrays, you can simple provide an empty array when you don't want to emit anything
...
.catch(error => return [];)

How to "replay" the last emitted item to each subscriber?

In RxJS I want to give every new subscriber the last item that was emitted. But how do I do that in an Observable chain?
this.http.get().map().replaySubject().refCount()
This answer refers to RxJS 5:
One way would be to use publishReplay:
this.http
.get()
.map()
.publishReplay(1)
.refCount();
If your source is a source, that completes (which would be typical for a rest-call, since it completes after a response is received), you could also use publishLast:
this.http
.get()
.map()
.publishLast()
.refCount();
And a third way (which gives you the most flexibility) would be to use an external BehaviorSubject or a ReplaySubject:
public myData$: BehaviorSubject<any> = new BehaviorSubject(null); // initial value is "null"
public requestData(): BehaviorSubject<any> {
this.http
.get()
.map()
.do(data => this.myData$.next(data))
.subscribe();
return this.myData$.skip(1); // the returned subject skips 1, because this would be the current value - of course the skip is optional and depends on the implementation of the requesting component
}
In your component(s) you can the get the data via myData$.subscribe(...) for getting the currently "cached" data or via requestData().subscribe(...) for the latest data.

Resources