Shortest code to cache Rxjs http request while not complete? - javascript

I'm trying to create an observable flow that fulfills the following requirements:
Loads data from storage at subscribe time
If the data has not yet expired, return an observable of the stored value
If the data has expired, return an HTTP request observable that uses the refresh token to get a new value and store it
If this code is reached again before the request has completed, return the same request observable
If this code is reached after the previous request completed or with a different refresh token, start a new request
I'm aware that there are many different answers on how to perform step (3), but as I'm trying to perform these steps together I am looking for guidance on whether the solution I've come up with is the most succinct it can be (which I doubt!).
Here's a sample demonstrating my current approach:
var cachedRequestToken;
var cachedRequest;
function getOrUpdateValue() {
return loadFromStorage()
.flatMap(data => {
// data doesn't exist, shortcut out
if (!data || !data.refreshtoken)
return Rx.Observable.empty();
// data still valid, return the existing value
if (data.expires > new Date().getTime())
return Rx.Observable.return(data.value);
// if the refresh token is different or the previous request is
// complete, start a new request, otherwise return the cached request
if (!cachedRequest || cachedRequestToken !== data.refreshtoken) {
cachedRequestToken = data.refreshtoken;
var pretendHttpBody = {
value: Math.random(),
refreshToken: Math.random(),
expires: new Date().getTime() + (10 * 60 * 1000) // set by server, expires in ten minutes
};
cachedRequest = Rx.Observable.create(ob => {
// this would really be a http request that exchanges
// the one use refreshtoken for new data, then saves it
// to storage for later use before passing on the value
window.setTimeout(() => { // emulate slow response
saveToStorage(pretendHttpBody);
ob.next(pretendHttpBody.value);
ob.completed();
cachedRequest = null; // clear the request now we're complete
}, 2500);
});
}
return cachedRequest;
});
}
function loadFromStorage() {
return Rx.Observable.create(ob => {
var storedData = { // loading from storage goes here
value: 15, // wrapped in observable to delay loading until subscribed
refreshtoken: 63, // other process may have updated this between requests
expires: new Date().getTime() - (60 * 1000) // pretend to have already expired
};
ob.next(storedData);
ob.completed();
})
}
function saveToStorage(data) {
// save goes here
}
// first request
getOrUpdateValue().subscribe(function(v) { console.log('sub1: ' + v); });
// second request, can occur before or after first request finishes
window.setTimeout(
() => getOrUpdateValue().subscribe(function(v) { console.log('sub2: ' + v); }),
1500);

First, have a look at a working jsbin example.
The solution is a tad different then your initial code, and I'd like to explain why. The need to keep returning to your local storage, save it, save flags (cache and token) didn't not fit for me with reactive, functional approach. The heart of the solution I gave is:
var data$ = new Rx.BehaviorSubject(storageMock);
var request$ = new Rx.Subject();
request$.flatMapFirst(loadFromServer).share().startWith(storageMock).subscribe(data$);
data$.subscribe(saveToStorage);
function getOrUpdateValue() {
return data$.take(1)
.filter(data => (data && data.refreshtoken))
.switchMap(data => (data.expires > new Date().getTime()
? data$.take(1)
: (console.log('expired ...'), request$.onNext(true) ,data$.skip(1).take(1))));
}
The key is that data$ holds your latest data and is always up to date, it is easily accessible by doing a data$.take(1). The take(1) is important to make sure your subscription gets a single values and terminates (because you attempt to work in a procedural, as opposed to functional, manner). Without the take(1) your subscription would stay active and you would have multiple handlers out there, that is you'll handle future updates as well in a code that was meant only for the current update.
In addition, I hold a request$ subject which is your way to start fetching new data from the server. The function works like so:
The filter ensures that if your data is empty or has no token, nothing passes through, similar to the return Rx.Observable.empty() you had.
If the data is up to date, it returns data$.take(1) which is a single element sequence you can subscribe to.
If not, it needs a refresh. To do so, it triggers request$.onNext(true) and returns data$.skip(1).take(1). The skip(1) is to avoid the current, out dated value.
For brevity I used (console.log('expired ...'), request$.onNext(true) ,data$.skip(1).take(1))). This might look a bit cryptic. It uses the js comma separated syntax which is common in minifiers/uglifiers. It executes all statements and returns the result of the last statement. If you want a more readable code, you could rewrite it like so:
.switchMap(data => {
if(data.expires > new Date().getTime()){
return data$.take(1);
} else {
console.log('expired ...');
request$.onNext(true);
return data$.skip(1).take(1);
}
});
The last part is the usage of flatMapFirst. This ensures that once a request is in progress, all following requests are dropped. You can see it works in the console printout. The 'load from server' is printed several times, yet the actual sequence is invoked only once and you get a single 'loading from server done' printout. This is a more reactive oriented solution to your original refreshtoken flag checking.
Though I didn't need the saved data, it is saved because you mentioned that you might want to read it on future sessions.
A few tips on rxjs:
Instead of using the setTimeout, which can cause many problems, you can simply do Rx.Observable.timer(time_out_value).subscribe(...).
Creating an observable is cumbersome (you even had to call next(...) and complete()). You have a much cleaner way to do this using Rx.Subject. Note that you have specifications of this class, the BehaviorSubject and ReplaySubject. These classes are worth knowing and can help a lot.
One last note. This was quite a challange :-) I'm not familiar with your server side code and design considerations yet the need to suppress calls felt uncomfortable to me. Unless there is a very good reason related to your backend, my natural approach would be to use flatMap and let the last request 'win', i.e. drop previous un terminated calls and set the value.
The code is rxjs 4 based (so it can run in jsbin), if you're using angular2 (hence rxjs 5), you'll need to adapt it. Have a look at the migration guide.
================ answers to Steve's other questions (in comments below) =======
There is one article I can recommend. It's title says it all :-)
As for the procedural vs. functional approach, I'd add another variable to the service:
let token$ = data$.pluck('refreshtoken');
and then consume it when needed.
My general approach is to first map my data flows and relations and then like a good "keyboard plumber" (like we all are), build the piping. My top level draft for a service would be (skipping the angular2 formalities and provider for brevity):
class UserService {
data$: <as above>;
token$: data$.pluck('refreshtoken');
private request$: <as above>;
refresh(){
request.onNext(true);
}
}
You might need to do some checking so the pluck does not fail.
Then, each component that needs the data or the token can access it directly.
Now lets suppose you have a service that needs to act on a change to the data or the token:
class SomeService {
constructor(private userSvc: UserService){
this.userSvc.token$.subscribe(() => this.doMyUpdates());
}
}
If your need to synthesize data, meaning, use the data/token and some local data:
Rx.Observable.combineLatest(this.userSvc.data$, this.myRelevantData$)
.subscribe(([data, myData] => this.doMyUpdates(data.someField, myData.someField));
Again, the philosophy is that you build the data flow and pipes, wire them up and then all you have to do is trigger stuff.
The 'mini pattern' I've come up with is to pass to a service once my trigger sequence and register to the result. Lets take for example autocomplete:
class ACService {
fetch(text: string): Observable<Array<string>> {
return http.get(text).map(response => response.json().data;
}
}
Then you have to call it every time your text changes and assign the result to your component:
<div class="suggestions" *ngFor="let suggestion; of suggestions | async;">
<div>{{suggestion}}</div>
</div>
and in your component:
onTextChange(text) {
this.suggestions = acSVC.fetch(text);
}
but this could be done like this as well:
class ACService {
createFetcher(textStream: Observable<string>): Observable<Array<string>> {
return textStream.flatMap(text => http.get(text))
.map(response => response.json().data;
}
}
And then in your component:
textStream: Subject<string> = new Subject<string>();
suggestions: Observable<string>;
constructor(private acSVC: ACService){
this.suggestions = acSVC.createFetcher(textStream);
}
onTextChange(text) {
this.textStream.next(text);
}
template code stays the same.
It seems like a small thing here, but once the app grows bigger, and the data flow complicated, this works much better. You have a sequence that holds you data and you can use it around the component wherever you need it, you can even further transform it. For example, lets say you need to know the number of suggestions, in the first method, once you get the result, you need to further query it to get it, thus:
onTextChange(text) {
this.suggestions = acSVC.fetch(text);
this.suggestionsCount = suggestions.pluck('length'); // in a sequence
// or
this.suggestions.subscribe(suggestions => this.suggestionsCount = suggestions.length); // in a numeric variable.
}
Now in the second method, you just define:
constructor(private acSVC: ACService){
this.suggestions = acSVC.createFetcher(textStream);
this.suggestionsCount = this.suggestions.pluck('length');
}
Hope this helps :-)
While writing, I tried to reflect about the path I took to getting to use reactive like this. Needless to say that on going experimentation, numerous jsbins and strange failures are big part of it. Another thing that I think helped shape my approach (though I'm not currently using it) is learning redux and reading/trying a bit of ngrx (angular's redux port). The philosophy and the approach does not let you even think procedural so you have to tune in to functional, data, relations and flows based mindset.

Related

Caching observables causing problem with mergeMap

I have a caching method in a container:
get(): Observable<T[]> {
if (!this.get$) {
this.get$ = merge(
this.behaviorSubject.asObservable(),
this._config.get().pipe(shareReplay(1), tap(x => this.behaviorSubject.next(x))));
}
return this.get$;
}
This works fine with normal observables, however when I cache the bellow in a myContainer2 (e.g using cached observable's result to create another cached observable) method like:
// get is assigned to _config.get in the above function
const myContainer2 = new Container({get: () => myContainer1.get().pipe(mergeMap(res1 => getObs2(res1))});
// please note, the end goal is to resolve the first observable on the first subscription
// and not when caching it in the above method (using cold observables)
myContainer2.get().subscribe(...) // getObs2 gets called
myContainer2.get().subscribe(...) // getObs2 gets called again
myContainer2.get().subscribe(...) // getObs2 gets called for a third time, and so on
every time when the second cache is subscribed to getObs2 gets called (it caches nothing).
I suspect my implementation of get is faulty, since I am merging an behavior subject (which emits at the beginning), but I cant think of any other way to implement it (in order to use cold observables).
Please note that if I use normal observable instead of myContainer.get() everything works as expected.
Do you know where the problem lies?
Using a declarative approach, you can handle caching as follows:
// Declare the Observable that retrieves the set of
// configuration data and shares it.
config$ = this._config.get().pipe(shareReplay(1));
When subscribed to config$, the above code will automatically go get the configuration if it's not already been retrieved or return the retrieved configuration.
I'm not clear on what the BehaviorSubject code is for in your example. If it was to hold the emitted config data, it's not necessary as the config$ will provide it.

Angular subscribes not working how I expect

I'm at a loose end here and trying to understand the flow of how angular subscriptions work.
I make a call to an API and in the response I set the data in a behaviourSubject. So I can then subscribe to that data in my application.
Normally I would use async pipes in my templates cause its cleaner and it gets rid of all the subscription data for me.
All methods are apart of the same class method.
my first try.....
exportedData: BehaviourSubject = new BehaviourSubject([]);
exportApiCall(id) {
this.loadingSubject.next(true)
this.api.getReport(id).pipe(
catchError((err, caught) => this.errorHandler.errorHandler(err, caught)),
finalize(() => => this.loadingSubject.next(false))
).subscribe(res => {
this.exportedData.next(res)
})
}
export(collection) {
let x = []
this.exportCollection(collection.id); /// calls api
this.exportedData.subscribe(exportData => {
if(exportData){
x = exportData
}
})
}
console.log(x)//// first time it's empthy, then it's populated with the last click of data
/// in the template
<button (click)="export(data)">Export</button>
My problem is....
There is a list of buttons with different ID's. Each ID goes to the API and gives back certain Data. When I click, the console log firstly gives a blank array. Then there after I get the previous(the one I originally clicked) set of data.
I'm obviously not understanding subscriptions, pipes and behavior Subjects correctly. I understand Im getting a blank array because I'm setting the behaviour subject as a blank array.
my other try
export(collection) {
let x = []
this.exportCollection(collection.id).pip(tap(res => x = res)).subscribe()
console.log(x) //// get blank array
}
exportApiCall(id) {
return this.api.getReport(id).pipe(
catchError((err, caught) => this.errorHandler.errorHandler(err, caught))
)
}
Not sure about the first example - the placement of console.log() and what does the method (that is assigned on button click) do - but for the second example, you're getting an empty array because your observable has a delay and TypeScript doesn't wait for its execution to be completed.
You will most likely see that you will always receive your previous result in your console.log() (after updating response from API).
To get the initial results, you can update to such:
public exportReport(collection): void {
this.exportCollection(collection.id).pipe(take(1)).subscribe(res => {
const x: any = res;
console.log(x);
});
}
This will print your current iteration/values. You also forgot to end listening for subscription (either by unsubscribing or performing operators such as take()). Without ending listening, you might get unexpected results later on or the application could be heavily loaded.
Make sure the following step.
better to add console.log inside your functions and check whether values are coming or not.
Open your chrome browser network tab and see service endpoint is get hitting or not.
check any response coming from endpoints.
if it is still not identifiable then use below one to check whether you are getting a response or not
public exportReport(collection): void {
this.http.get(url+"/"+collection.id).subscribe(res=> {console.log(res)});
}
You would use BehaviourSubject, if there needs to be an initial/default value. If not, you can replace it by a Subject. This is why the initial value is empty array as BehaviourSubject gets called once by default. But if you use subject, it wont get called before the api call and you wont get the initial empty array.
exportedData: BehaviourSubject = new BehaviourSubject([]);
Also, you might not need to subscribe here, instead directly return it and by doing so you could avoid using the above subject.
exportApiCall(id) {
this.loadingSubject.next(true);
return this.api.getReport(id).pipe(
catchError((err, caught) => this.errorHandler.errorHandler(err, caught)),
finalize(() => => this.loadingSubject.next(false))
);
}
Console.log(x) needs to be inside the subscription, as subscribe is asynchronous and we dont knw when it might get complete. And since you need this data, you might want to declare in global score.
export(collection) {
// call api
this.exportApiCall(collection.id).subscribe(exportData => {
if (exportData) {
this.x = exportData; // or maybe this.x.push(exportData) ?
console.log(this.x);
}
});
}

switchMap operation only running on first call?

I have an angular application that makes a request to an Http service and calls a switchMap on another Http service. For some reason the request in the switchMap only runs the first time the parent call is called. Otherwise the parent request fires and the switchMap one doesn't, here is the code:
this._receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result =>
// Refresh the lease receivables before giving result
this._receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
}).take(1).map(() => result)
)
.subscribe(
...
)
How can I make the getAll request in the switch map run every time the newTenantDebitCredit method is called above it?
Edit: Here is the entirety of the function that is called on click. when i click the button the first time for a given unit both methods are executed. If I try a Unit that has already had that method called (without a refresh) only the first method is executed. I realize a lot of this may not be clear it's a rather large project at this point.
public submitTenantCredit() {
this.isLoading = true;
let tenantCredit: NewTenantDebitCreditData;
let receivableDefinitions: ReceivableDefinition[] = [];
// construct receivable defintions for NewTenantDebitData model
receivableDefinitions = this._constructReceivableDefinitions();
// construct data we will be POSTing to server.
tenantCredit = new NewTenantDebitCreditData({
siteId: this._apiConfig.siteId,
leaseId: this.leaseId,
isCredit: true,
receivables: receivableDefinitions,
reason: this.actionReason
});
// make service call and handle response
this._receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result =>
// Refresh the lease receivables before giving result
this._receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
}).take(1).map(() => result)
)
.take(1)
.subscribe(
(receivables) => {
this.closeReasonModal();
let refreshLeaseId = this.leaseId;
this.leaseId = refreshLeaseId;
this.isLoading = false;
this.refreshBool = !this.refreshBool;
this._debitCreditService.refreshUnitInfo();
this._notifications.success(`The tenant credit for ${this.customerName} - Unit ${this.unitNumber} was submitted successfully`);
},
(error) => {
console.error(error);
this.isLoading = false;
}
)
}
If it helps newTenantDebitCredit() is a HTTP POST request and getAll() is a GET request.
You used take operator. When your service observable will emit then take operator will execute first and take will chain only first emit from observable. Subsequent emit will not taken by your code.
If you want to take all emits from observable then remove take from your code.
Hope it will help.
Testing the Rx code in isolation, here's a mockup. The console logs happen each time, so I think the Rx you're using is ok.
The best guess at a likely culprit is this.refreshBool = !this.refreshBool, but we'd need to see the internals of newTenantDebitCredit and getAll to be definitive.
// Some mocking
const _receivableService = {
newTenantDebitCredit: (tc) => {
console.log('inside newTenantDebitCredit')
return Rx.Observable.of({prop1:'someValue'})
},
getAll: (options) => {
console.log('inside getAll')
return Rx.Observable.of({prop2:'anotherValue'})
}
}
const tenantCredit = {}
// Test
_receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result => {
console.log('result', result)
return _receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
})
.take(1)
.map(() => result)
})
.take(1)
.subscribe(
(receivables) => {
console.log('receivables', receivables)
//this.refreshBool = !this.refreshBool;
},
(error) => {
console.error(error);
}
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
First of all, this has nothing to do with the switchMap operator.
Normaly removing the take(1) would cause this behaviour. In this case it wouldn't because it itsn't a so called hot observable.
The problem is that you are using a http.post. This is a cold observable which means it will only return a value once. That is also the reason why you don't need to unsubscribe. It will NEVER fire twice. Possible sollutions might be:
Using web sockets to get realtime data.
Creating a timer which will periodically fetch the data.
Simply get the data again whenever you need it.
The way you are asking the question
How can I make the getAll request in the switch map run every time the newTenantDebitCredit method is called above it?
actually sounds to me as if you are calling only newTenantDebitCredit from somewhere in your code, expecting the second request to happen; so I think this might be a misunderstanding of how observable chains work. Let's make an example:
const source$ = Observable.of(42);
source$
.map(value => 2 * value)
.subscribe(console.log);
source$
.subscribe(console.log);
What would you expect this to log? If your answer is "It would log 84 twice", then that is wrong: it logs 84 and 42.
Conceptually, your situation is the same. The second request only happens when the observable returned by newTenantDebitCredit() emits; it will not happen anytime some caller calls newTenantDebitCredit. This is because observable chains do not mutate an observable in-place, they only ever return a new observable.
If you want the second request to happen, you have to actually change the definition of the newTenantDebitCredit method to return an observable set up to perform the second request; alternatively, set up a chained observable that you subscribe to instead of calling newTenantDebitCredit.
Not really an answer but I did solve my problem. It will almost certainly be of no use to anyone BUT it was an issue in the receivableService it was not properly cheeking the boolean: refresh and was pulling values from cache after the first time.

React - controlling async calls smartly without any side effect in complex applications

Solution proposed by codeslayer1 in question raised at React - Controlling multiple Ajax Calls has an issue of accessing state directly inside action creator - an anti pattern.
So, if I don't access the state inside my action creator what I will do is, I will listen to a batchRequestCompleted state in my component. When components prop batchRequestCompleted will become true(means previous request is completed), I will check if any pending requests are there. If yes, I will dispatch action to process those next requests. So basically saga calls action which in turn modifies the state. And once state is modified, another action to process further requests is dispatched from component. In this way, saga never accesses the state.
Solution above sounds good but comes at a cost of problem mentioned in Route change before action creator completes. That is, what will happen to the requests placed inside queue if someone navigates to a different route, before queue is cleared.
Can I solve the problem mentioned in React - Controlling multiple Ajax Calls without accessing state inside action creators and without bringing component back in picture for dispatching an action to clear the pending queue.
Note: I have created a new question because problem mentioned in React - Controlling multiple Ajax Calls is solved but with side effects and this question majorly focuses on reaching to a solution which cleans off that side effect.
I made a little repo github.com/adz5a/so-stream-example to illustrate how I would solve your problem.
This repo uses two libraries xstream and recompose. The former provides an implementation of ObservableStreams with its operators and the latter wires it up with React.
A concept is necessary before everything : ES Observables. They are covered in depth in articles such as this (I strongly recommend reading and listening to past articles / talks from Ben Lesh, on this subject).
Observabes are a lazy primitive used to model values over time. In JS we have another primitive for doing async : Promises. Those models an eventual value or error and thus are not lazy but eager. In the case of a React component ( or more generally UI ) we are interested in lazyness because things can go wrong : the user may want to interrupt a long running process, it can crash, change route etc...
So, how can we solve your problem : controlling a long running process which can be interrupted ( fetching lots of rows ) by user interaction ?
First, the UI :
export class AnswerView extends React.Component {
static propTypes = {
// called when the user make a batch
// of request
onStart: PropTypes.func.isRequired,
// called when you want to stop the processing
// of requests ( when unmounting or at the request
// of the user )
onStop: PropTypes.func.isRequired,
// number of requests completed, 0 by default
completedRequests: PropTypes.number.isRequired,
// whether it's working right now or not
processing: PropTypes.bool.isRequired
};
render () {
// displays a form if no work is being done,
// else the number of completed requests
return (
<section>
<Link to="/other">Change Route !</Link>
<header>
Lazy Component Example
</header>
{
this.props.processing ?
<span>{"requests done " + this.props.completedRequests}<button onClick={this.props.onStop}>Stop !</button></span>:
<form onSubmit={e => {
e.preventDefault();
this.props.onStart(parseInt(e.currentTarget.elements.number.value, 10));
}}>
Nb of posts to fetch<input type="number" name="number" placeholder="0"/>
<input type="submit" value="go"/>
</form>
}
</section>
);
}
componentWillMount () {
console.log("mounting");
}
}
Pretty simple : a form with an input for the number of requests to perform (could checkboxes on a table component ... ).
Its props are as follow :
onStart : fn which takes the desired number
onStop : fn which takes no args and signals we would like to stop. Can be hooked to a button or in this case, componentWillUnmout.
completedRequests: Integer, counts requests done, 0.
processing: boolean, indicates if work is under way.
This does not do much by itself, so let's introduce recompose. Its purpose is to enhance component via HOC. We will use the mapPropsStream helper in this example.
Note : in this answer I use stream / Observable interchangeably but this is not true in the general case. A stream is an Observable with operators allowing to transform the emitted value into a new Observable.
For a React Component we can sort of observe its props with the standard api : 1st one at componentWillMount, then at componentWillReceiveProps. We can also signal when there will be no more props with componentWillUnmount. We can build the following (marble) diagram : p1--p2--..--pn--| (the pipe indicates the completion of the stream).
The enhancer code is posted below with comments.
What needs to be understood is that everything with streams can be approached like a signal : by modelling everything as a stream we can be sure that by sending the appropriate signal we can have the desired behaviour.
export const enhance = mapPropsStream(prop$ => {
/*
* createEventHandler will help us generates the callbacks and their
* corresponding streams.
* Each callback invocation will dispatch a value to their corresponding
* stream.
*/
// models the requested number of requests
const { handler: onStart, stream: requestCount$ } = createEventHandler();
// models the *stop* signals
const { handler: onStop, stream: stop$ } = createEventHandler();
// models the number of completed requests
const completedRequestCount$ = requestCount$.map( n => {
// for each request, generate a dummy url list
const urls = Array.from({ length: n }, (_, i) => `https://jsonplaceholder.typicode.com/posts/${i + 1}` );
// this is the trick : we want the process to be aware of itself when
// doing the next operation. This is a circular invocation so we need to
// use a *proxy*. Note : another way is to use a *subject* but they are
// not present in __xstream__, plz look at RxJS for a *subject* overview
// and implementation.
const requestProxy$ = xs.create();
const count$ = requestProxy$
// a *reduce* operation to follow where we are
// it acts like a cursor.
.fold(( n ) => n + 5, 0 )
// this will log the current value
.debug("nb");
const request$ = count$.map( n => Promise.all(urls.slice(n, n + 5).map(u => fetch(u))) )
.map(xs.fromPromise)
.flatten()
.endWhen(xs.merge(
// this stream completes when the stop$ emits
// it also completes when the count is above the urls array length
// and when the prop$ has emitted its last value ( when unmounting )
stop$,
count$.filter(n => n >= urls.length),
prop$.last()
));
// this effectively activates the proxy
requestProxy$.imitate(request$);
return count$;
} )
.flatten();
// models the processing props,
// will emit 2 values : false immediately,
// true when the process starts.
const processing$ = requestCount$.take(1)
.mapTo(true)
.startWith(false);
// combines each streams to generate the props
return xs.combine(
// original props
prop$,
// completed requests, 0 at start
completedRequestCount$.startWith(0),
// boolean indicating if processing is en route
processing$
)
.map(([ props, completedRequests, processing ]) => {
return {
...props,
completedRequests,
processing,
onStart,
onStop
};
})
// allows us to catch any error generated in the streams
// very much equivalent to the new ErrorBoundaries in React
.replaceError( e => {
// logs and return an empty stream which will never emit,
// effectively blocking the component
console.error(e);
return xs.empty();
} );
});
export const Answer = enhance(AnswerView);
I hope this answer is not (too) convoluted, feel free to ask any question.
As a side note, after a little research you may notice that the processing boolean is not really used in the logic but is merely there to help the UI know what's going on : this is a lot cleaner than having some piece of state attached to the this of a Component.

Subject vs BehaviorSubject vs ReplaySubject in Angular

I've been looking to understand those 3:
Subject
BehaviorSubject
ReplaySubject
I would like to use them and know when and why, what are the benefits of using them and although I've read the documentation, watched tutorials and searched google I've failed to make any sense of this.
So what are their purpose? A real-world case would be most appreciated it does not have to even code.
I would prefer a clean explanation not just "a+b => c you are subscribed to ...."
Thank you
It really comes down to behavior and semantics. With a
Subject - a subscriber will only get published values that were emitted after the subscription. Ask yourself, is that what you want? Does the subscriber need to know anything about previous values? If not, then you can use this, otherwise choose one of the others. For example, with component-to-component communication. Say you have a component that publishes events for other components on a button click. You can use a service with a subject to communicate.
BehaviorSubject - the last value is cached. A subscriber will get the latest value upon initial subscription. The semantics for this subject is to represent a value that changes over time. For example a logged in user. The initial user might be an anonymous user. But once a user logs in, then the new value is the authenticated user state.
The BehaviorSubject is initialized with an initial value. This is sometimes important to coding preference. Say for instance you initialize it with a null. Then in your subscription, you need to do a null check. Maybe OK, or maybe annoying.
ReplaySubject - it can cache up to a specified number of emissions. Any subscribers will get all the cached values upon subscription. When would you need this behavior? Honestly, I have not had any need for such behavior, except for the following case:
If you initialize a ReplaySubject with a buffer size of 1, then it actually behaves just like a BehaviorSubject. The last value is always cached, so it acts like a value changing over time. With this, there is no need for a null check like in the case of the BehaviorSubject initialized with a null. In this instance, no value is ever emitted to the subscriber until the first publishing.
So it really comes down to the behavior you are expecting (as for which one to use). Most of the time you will probably want to use a BehaviorSubject because what you really want to represent is that "value over time" semantic. But I personally don't see anything wrong with the substitution of ReplaySubject initialized with 1.
What you want to avoid is using the vanilla Subject when what you really need is some caching behavior. Take for example you are writing a routing guard or a resolve. You fetch some data in that guard and set it in a service Subject. Then in the routed component you subscribe to the service subject to try to get that value that was emitted in the guard. OOPs. Where's the value? It was already emitted, DUH. Use a "caching" subject!
See also:
What are RxJS Subject's and the benefits of using them?
Subject: On subscribing it always gets the data which is pushed after it's subscription i.e. previous pushed values are not received.
const mySubject = new Rx.Subject();
mySubject.next(1);
const subscription1 = mySubject.subscribe(x => {
console.log('From subscription 1:', x);
});
mySubject.next(2);
const subscription2 = mySubject.subscribe(x => {
console.log('From subscription 2:', x);
});
mySubject.next(3);
subscription1.unsubscribe();
mySubject.next(4);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.min.js"></script>
With this example, here’s the result that’ll be printed in the console:
From subscription 1: 2
From subscription 1: 3
From subscription 2: 3
From subscription 2: 4
Note how subscriptions that arrive late are missing out on some of the data that’s been pushed into the subject.
Replay subjects: can help by keeping a buffer of previous values that will be emitted to new subscriptions.
Here’s a usage example for replay subjects where a buffer of 2 previous values are kept and emitted on new subscriptions:
const mySubject = new Rx.ReplaySubject(2);
mySubject.next(1);
mySubject.next(2);
mySubject.next(3);
mySubject.next(4);
mySubject.subscribe(x => {
console.log('From 1st sub:', x);
});
mySubject.next(5);
mySubject.subscribe(x => {
console.log('From 2nd sub:', x);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.min.js"></script>
Here’s what that gives us at the console:
From 1st sub: 3
From 1st sub: 4
From 1st sub: 5
From 2nd sub: 4
From 2nd sub: 5
Behavior subjects: are similar to replay subjects, but will re-emit only the last emitted value, or a default value if no value has been previously emitted:
const mySubject = new Rx.BehaviorSubject('Hey now!');
mySubject.subscribe(x => {
console.log('From 1st sub:', x);
});
mySubject.next(5);
mySubject.subscribe(x => {
console.log('From 2nd sub:', x);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.12/Rx.min.js"></script>
And the result:
From 1st sub: Hey now!
From 1st sub: 5
From 2nd sub: 5
Reference: https://alligator.io/rxjs/subjects/
A handy summary of the different observable types, non intuitive naming i know lol.
Subject - A subscriber will only get published values thereon-after the subscription is made.
BehaviorSubject - New subscribers get the last published value OR initial value immediately upon subscription.
ReplaySubject - New subscribers get all previously published value(s) immediately upon subscription
Most upvoted answer is plainly wrong claiming that:
"If you initialize a ReplaySubject with a buffer size of 1, then it actually behaves just like a BehaviorSubject"
This is not totally true; check this great blog post on differences between those two. For example if you subscribe to a completed BehaviorSubject, you won’t receive the last value but for a ReplaySubject(1) you will receive the last value.
This is am important difference that should not be overlooked:
const behavior = new BehaviorSubject(null);
const replay = new ReplaySubject(1);
behavior.skip(1).subscribe(v => console.log('BehaviorSubject:', v));
replay.subscribe(v => console.log('ReplaySubject:', v));
behavior.next(1);
behavior.next(2);
behavior.complete();
behavior.subscribe(v => console.log('Late B subscriber:', v));
replay.next(1);
replay.next(2);
replay.complete();
replay.subscribe(v => console.log('Late R subscriber:', v));
Check this code example here which comes from another great blog post on the topic.
From: Randall Koutnik's book “Build Reactive Websites with RxJS.” :
A Subject is an object that’s a turbocharged observable. At its core, a Subject acts much like a regular observable, but each subscription is hooked into the same source. Subjects also are observers and have next, error, and done methods to send data to all subscribers at once. Because subjects are observers, they can be passed directly into a subscribe call, and all the events from the original observable will be sent through the subject to its subscribers.
We can use the ReplaySubject to track history. A ReplaySubject records the last n events and plays them back to every new subscriber. For example in a chat applications. We can use it for tracking the record of previous chat history.
A BehaviorSubject is a simplified version of the ReplaySubject.
The ReplaySubject stored an arbitrary number of events, the BehaviorSubject only records the value of the latest event. Whenever a BehaviorSubject records a new subscription, it emits the latest value to the subscriber as well as any new values that are passed in. The BehaviorSubject is useful when dealing with single units of state, such as configuration options.
As mentioned in some of the posts, the accepted answer is wrong since BehaviorSubject != ReplaySubject(1) and it's not just a preference of coding style.
In the comments often the "guards" are mentioned and that's also where I most often found the use case for the Replay subjects. More specifically if you have a take(1) like scenario and you don't just want to take the initial value.
Check for example the following:
ngOnInit() {
const behaviorSubject = new BehaviorSubject<boolean>(null);
const replaySubject = new ReplaySubject<boolean>(1);
this.checkLoggedIn(behaviorSubject, 'behaviorSubject');
this.checkLoggedIn(replaySubject, 'replaySubject');
behaviorSubject.next(true);
replaySubject.next(true);
}
checkLoggedIn($userLoggedIn: Observable<boolean>, id: string) {
$userLoggedIn.pipe(take(1)).subscribe(isLoggedIn => {
if (isLoggedIn) {
this.result[id] = 'routed to dashboard';
} else {
this.result[id] = 'routed to landing page';
}
});
}
with the result:
{
"behaviorSubject": "routed to landing page",
"replaySubject": "routed to dashboard"
}
In those cases clearly you'd want a ReplaySubject! Working code: https://stackblitz.com/edit/replaysubject-vs-behaviorsubject?file=src%2Fapp%2Fapp.component.ts
// ***********Subject concept ***********
let subject = new Subject<string>();
subject.next("Eureka");
subject.subscribe((data) => {
console.log("Subscriber 1 got data >>>>> "+ data);
});
subject.subscribe((data) => {
console.log("Subscriber 2 got data >>>>> "+ data);
});
// ********behaviour subject*********
// Behavior subjects need a first value
let subject1 = new BehaviorSubject<string>("First value");
subject1.asObservable().subscribe((data) => {
console.log("First subscriber got data behaviour subject>>>>> "+ data);
});
subject1.next("Second value")
Subject - A subscriber will only get published values thereon-after the subscription is made.
BehaviorSubject - New subscribers get the last published value OR initial value immediately upon subscription.
Another difference is you can use the value getter of BehaviorSubject to get the current value. This is very useful when you need just current value in certain circumstances. For example, when a user clicks something and you need the value only once. In this case, you don't need to subscribe and then unsubscribe suddenly. The only need is:
BehaviorSubject bSubject = new BehaviorSubject<IBasket>(basket);
getCurrentBasketValue() {
return this.bSubject.value;
}

Categories

Resources