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;
}
Related
I have an observable (onAuthStateChanged) from the Firebase client that:
emits null immediately if the user is not signed in, and
emits null and then a user object a few moments later if the user is signed in.
const observable = new Observable((obs) => {
return app.auth().onAuthStateChanged(
obs.next,
obs.error,
obs.complete
)
})
What I want is to:
ignore any emitted null values for the first 1000ms of the app lifecycle (null coming after 1000ms is accepted)
always emit user object regardless of what time it comes
if no user object comes in the first 1000ms, then emit null at the 1000ms mark
Here is what I've done (and it seems to work). However, I'm reluctant to use this code as it doesn't seem that concise:
const o1 = observable.pipe(skipUntil(timer(1000)))
const o2 = observable.pipe(
takeUntil(o1),
filter((user) => user !== null)
)
const o3 = timer(1000).pipe(takeUntil(o2), mapTo(null))
merge(o1, o2, o3).subscribe({
next: setUser,
error: console.log,
complete: () => console.log("error: obs completed, this shouldn't happen"),
})
Is there a way to do this without merge? I tried going through the docs but I'm quite lost.
Thanks for your help!
You could use concat instead of merge. Think of it as using the first source until it completes, then use the second source.
const nonNullUser = firebaseUser.pipe(
filter(user => user !== null),
takeUntil(timer(1000))
);
const user = concat(nonNullUser, firebaseUser);
user.subscribe(...);
I just realized that this solution will not explicitly perform step #3 "emit null at the 1000ms mark". I was thinking subscribing to firebaseUser would emit the latest value. But, I'm not sure if that's true for your scenario.
If not, we could easily achieve this by adding shareReplay like this:
const firebaseUser = observable.pipe(shareReplay(1));
While I liked the answer from #BizzyBob I was genuinely intrigued by these requirements that I wanted to see what other options were available. Here's what I produced:
const auth$ = observable.pipe(
startWith(null)
)
const null$ = timer(1000).pipe(
switchMap(_=>auth$)
)
const valid$ = auth$.pipe(
filter(user=>!!user)
)
const user$ = race(null$, valid$);
We have our source auth$ observable which gets your Firebase data. However, startWith() will immediately emit null before any values coming from Firebase.
I declared two observables for null and non-null cases, null$ and valid$.
The null$ observable will subscribe to auth$ after 1000ms. When this happens it immediately emits null thanks to the startWith() operator.
The valid$ observable subscribes to auth$ immediately but only emits valid user data thanks to filter(). It won't emit startWith(null) because it is caught by the filter.
Last, we declare user$ by using the race() operator. This operator accepts a list of observables as its parameters. The first observable to emit a value wins and is the resulting subscription.
So in our race, valid$ has 1000ms to emit a valid user. If it doesn't, race() will subscribe to null$ resulting in the immediate null, and all future values coming from Firebase.
I was going through this blog and reading about Observables and couldn't figure out the difference between the Observable and a Subject.
In stream programming there are two main interfaces: Observable and Observer.
Observable is for the consumer, it can be transformed and subscribed:
observable.map(x => ...).filter(x => ...).subscribe(x => ...)
Observer is the interface which is used to feed an observable source:
observer.next(newItem)
We can create new Observable with an Observer:
var observable = Observable.create(observer => {
observer.next('first');
observer.next('second');
...
});
observable.map(x => ...).filter(x => ...).subscribe(x => ...)
Or, we can use a Subject which implements both the Observable and the Observer interfaces:
var source = new Subject();
source.map(x => ...).filter(x => ...).subscribe(x => ...)
source.next('first')
source.next('second')
Observables are unicast by design and Subjects are multicast by design.
If you look at the below example, each subscription receives the different values as observables developed as unicast by design.
import {Observable} from 'rxjs';
let obs = Observable.create(observer=>{
observer.next(Math.random());
})
obs.subscribe(res=>{
console.log('subscription a :', res); //subscription a :0.2859800202682865
});
obs.subscribe(res=>{
console.log('subscription b :', res); //subscription b :0.694302021731573
});
This could be weird if you are expecting the same values on both the subscription.
We can overcome this issue using Subjects. Subjects is similar to event-emitter and it does not invoke for each subscription. Consider the below example.
import {Subject} from 'rxjs';
let obs = new Subject();
obs.subscribe(res=>{
console.log('subscription a :', res); // subscription a : 0.91767565496093
});
obs.subscribe(res=>{
console.log('subscription b :', res);// subscription b : 0.91767565496093
});
obs.next(Math.random());
Both of the subscriptions got the same output value!
Observables
They are cold: Code gets executed when they have at least a single observer.
Creates copy of data: Observable creates copy of data for each observer.
Uni-directional: Observer can not assign value to observable(origin/master).
The code will run for each observer . If its a HTTP call, it gets called for each observer.
if its a service we want to share among all the components, it wont have latest result all new subscribers will still subscribe to same observable and get value from scratch
Unicast means can emit values from the observable not from any other component.
Subject
They are hot: code gets executed and value gets broadcast even if there is no observer.
Shares data: Same data get shared between all observers.
bi-directional: Observer can assign value to observable(origin/master).
If are using using subject then you miss all the values that are broadcast before creation of observer. So here comes Replay Subject
multicast, can cast values to multiple subscribers and can act as both subscribers and emmitter
I found the accepted answer slightly confusing!
An Observer isn't the interface for feeding an Observable source, it's the interface for observing an Observable source... which makes more sense from the name, right?
So, the reason that:
var observable = Observable.create(observer => {
observer.next('first');
observer.next('second');
...
});
works - creating an observable which emits 'first' then 'second' - is that the argument to Observable.create(...) is a subscribe function, it basically defines which Observer events will happen on a direct Observer of that Observable.
If you want to go into it a little bit further again, it's important to understand that the subscribe function isn't directly called on the Observer object when you subscribe, instead it's mediated by a Subscription object which can enforce correct observable rules, e.g. that an Observable will never emit a new value after observer.complete() has been called, even if your subscribe function looks as if it would.
REF: http://reactivex.io/rxjs/manual/overview.html#creating-observables
A Subject is both an Observable and an Observer and once again it looks just like the Observer interface is the way to 'feed' events to the Subject. But it's easier to understand the naming if you realise that a Subject is a bit like an Observable with the equivalent of the subscribe function (i.e. where you define what events will happen to things observing it) sitting there right on the object, even after it has been created. So, you call Observer methods on the Subject to define what Observer events will happen on things observing it! 😊 (And again, there are intermediate objects involved, to make sure that you can only do legal sequences of things.)
REF: http://reactivex.io/rxjs/manual/overview.html#subject
See rxjs document (more information and examples there):
http://reactivex.io/rxjs/manual/overview.html#subject
What is a Subject? An RxJS Subject is a special type of Observable that allows values to be multicasted to many Observers. While plain Observables are unicast (each subscribed Observer owns an independent execution of the Observable), Subjects are multicast.
A Subject is like an Observable, but can multicast to many Observers. Subjects are like EventEmitters: they maintain a registry of many listeners.
and code, Subject extending Observable: https://github.com/ReactiveX/rxjs/blob/master/src/internal/Subject.ts#L22
/**
* #class Subject<T>
*/
export class Subject<T> extends Observable<T> implements SubscriptionLike {
//...
}
Imagine if you have a stream of data coming into your application like in a websocket connection. You want a way to handle it. There is a few solution:
1. normal ajax request:
This solution is not viable because it is
not applicable to process push data. It is more of a pull then a
push.
2. Promise:
Also not good because you have to trigger them and
they can only retrieve once. Also more of a pull then a push.
So in order to retrieve this data, in the old time, we do a long-polling. Which is where we set an interval function to retrieve that stream of data every 1 minute for an example. Though it works, it actually burdening resources like CPU and memory.
But now with option no 3,
3. Observable: You can subscribe and let the stream of data to come
in non-stop until the function complete has been called.
Cool right ? But then there is another problem. What if you want to observe incoming data only once somewhere in your application. But you want to use that data simultaneously around your application when the data arrived. That is when and where you use Subject.
You place subject.subscribe() at places you want to use throughout your application. When the data arrived, places where there is subject.subscribe() will process them simultaneously. But the observer must subscribe with the subject as its argument like this.
observer.subscribe(subject).
Example application is when you want to build a notification alert.
You cannot have multiple subscription of the same observable because chances are, each subscribers will received different input data. But with subject, all that subscribe() through subject will be retrieving the same data.
Another analogy is through magazine subscription. Each subscribers will received the magazine with their name on it. So, different subscription = different receiver name.(Normal Observable)
But when you share with your friends, all of your friend would receive the same magazine with only your name on it.(Normal Observable with Subject)
This guy explain it very well with code example. You can check it out at https://javascript.tutorialhorizon.com/2017/03/23/rxjs-subject-vs-observable/
Hopefully this answer helps.
Briefly,
subject: you can send to it and receive from it.
Observable: you can receive from it only.
In another words,
In subject you can subscribe to it and you can use it to broadcast to other subscribers any time and anywhere in code.
whilst,
in observable you can subscribe to it only (you can't use it to broadcast data after it have been initialized).
The only place you can broadcast data from observable is inside its constructor.
Observable can inform only one observer, while Subject can inform multiple observers.
From another perspective, it is good to note that the subscription to an Observable re-execute the Observable function. This can lead performance issue if the data source is a service for instance.
If you want several subscribers to get the same value, you may need a Subject.
For this, make sure that your subscription is set before the Subject subscribed to the data source. Otherwise, your process would be stuck.
More details here: https://javascript.tutorialhorizon.com/2017/03/23/rxjs-subject-vs-observable/
Observable:
Only the Observable knows how and when the events are triggered on the observable. i.e the next() method has to be called only inside the instantiated constructor. Also, on subscribing each time, a separate observer is created and calls next() method using particular observer inside constructor only, in the following example subscriber itself is the observer and it is subscribed when the instantiated constructor gets executed.
Ex:
import { Observable } from 'rxjs';
const observable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
setTimeout(() => {
subscriber.next(3);
}, 1000);
});
Subject:
Here next() method can be used by subject anywhere outside the constructor. Also, when next() method is called before subscribing, the particular event will be missed. Hence next() method has to be called only after subscribing.
Ex:
import { Subject } from 'rxjs';
const subject = new Subject<number>();
subject.next(1); // this is missed
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
subject.next(2);
I hold state in one ReplaySubject that replays the last copy of the state. From that state, other ReplaySubjects are derived to hold...well, derived state. Each replay subject need only hold it's last calculated state/derived state. (We don't use BehaviorSubjects because they always give a value, but we only want a value derived from our parent observables.) It is always necessary to replay the value to new subscribers if we have already generated derived state.
I have a custom observable operator that accomplishes this in just the way I want it to, but it doesn't feel that clean. I feel like there should be an efficient way to accomplish this with RxJ's operators themselves.
I have tried the two most obvious approaches, but there are slight problems with each. The problem involves unsubscribing and re-subscribing.
Open the fiddle below, open your console, and click run. I will describe the problem with each output.
https://jsfiddle.net/gfe1nryp/1/
The problem with a refCounted ReplaySubject
=== RefCounted Observable ===
Work
Subscription 1: 1
Work
Subscription 1: 2
Work
Subscription 1: 3
Unsubscribe
Resubscribe
Subscription 2: 3
Work
Subscription 2: 6
Work
Subscription 2: 7
Work
Subscription 2: 8
This works well, the intermediate functions don't do any work when there is nothing subscribed. However, once we resubscribe. We can see that Subscription 2 replays the last state before unsubscribe, and then plays the derived state based on the current value in the base$ state. This is not ideal.
The problem with connected ReplaySubject
=== Hot Observable ===
Work
Subscription 1: 1
Work
Subscription 1: 2
Work
Subscription 1: 3
Unsubscribe
Work
Work
Work
Resubscribe
Subscription 2: 6
Work
Subscription 2: 7
Work
Subscription 2: 8
This one does not have the same problem as the refCounted observable, there is no unnecessary replay of the last state before the unsubscription. However, since the observable is now hot, the tradeoff is that we always do work whenever a new value comes in the base$ state, even though the value is not used by any subscriptions.
Finally, we have the custom operator:
=== Custom Observable ===
Work
Subscription 1: 1
Work
Subscription 1: 2
Work
Subscription 1: 3
Unsubscribe
Resubscribe
Work
Subscription 2: 6
Work
Subscription 2: 7
Work
Subscription 2: 8
Ahh, the best of both worlds. Not only does it not unnecessarily replay the last value before unsubscription, but it also does not unnecessarily do any work when there is no subscription.
This is accomplished by manually creating a combination of RefCount and ReplaySubject. We keep track of each subscriber, and when it hits 0, we flush the replay value. The code for it is here (and in the fiddle, of course):
Rx.Observable.prototype.selectiveReplay = function() {
let subscribers = [];
let innerSubscription;
let storage = null;
return Rx.Observable.create(observer => {
if (subscribers.length > 0) {
observer.next(storage);
}
subscribers.push(observer);
if (!innerSubscription) {
innerSubscription = this.subscribe(val => {
storage = val;
subscribers.forEach(subscriber => subscriber.next(val))
});
}
return () => {
subscribers = subscribers.filter(subscriber => subscriber !== observer);
if (subscribers.length === 0) {
storage = null;
innerSubscription.unsubscribe();
innerSubscription = null;
}
};
});
};
So, this custom observable already works. But, can this be done with only RxJS operators? Keep in mind, potentially there could be more than a couple of these subjects linked together like this. In the example, I'm only using one linking to the base$ to illustrate the issue with both vanilla approaches I've tried at the most basic level.
Basically, if you can use only RxJS operators, and get the output to match the output for === Custom Observable === above. That's what I'm looking for. Thanks!
You should be able to use multicast with a subject factory instead of a subject. Cf. https://jsfiddle.net/pto7ngov/1/
(function(){
console.log('=== RefCounted Observable ===');
var base$ = new Rx.ReplaySubject(1);
var listen$ = base$.map(work).multicast(()=> new Rx.ReplaySubject(1)).refCount();
var subscription1 = listen$.subscribe(x => console.log('Subscription 1: ' + x));
base$.next(1);
base$.next(2);
base$.next(3);
console.log('Unsubscribe');
subscription1.unsubscribe();
base$.next(4);
base$.next(5);
base$.next(6);
console.log('Resubscribe');
var subscription2 = listen$.subscribe(x => console.log('Subscription 2: ' + x));
base$.next(7);
base$.next(8);
})();
This overload of the multicast operator serves exactly your use case. Every time the observable returned by the multicast operator completes and is reconnected to, it creates a new subject using the provided factory. It is not very well documented though, but it basically replicates an existing API from Rxjs v4.
In case I misunderstood or that does not work let me know,
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.
I am using observable in Angular2. As I know so far, each Observable instance come with an observer(1:1), and when we emit something with observer.next(value) we can get that value with observable.subscribe((value) => {}).
var observable = Observable.create(observer => {
observer.next(value);
}
.map(value=>{})
.catch(...)
observable.subscribe(value => {
console.log(value);
})
How can I emit value without knowing the corresponding observer, because I want to emit value outside create function. One possible solution is save observer into some global variable but I think an observable should be enough. Any suggestion for this ??
You're mixing multiple things together. Observables are not in 1:1 relation with Observers (more precisely it's 1:N). If you want to be able to manually emit values you need a Subject which acts as an Observable and an Observer at the same time. Practically this means you can call its next() method and it'll propage the value to all its subscribers (Observers).
For example consider the following code in TypeScript:
import {Subject} from 'rxjs';
let source = new Subject();
source.subscribe(val => console.log('Observer 1:', val));
source.subscribe(val => console.log('Observer 2:', val));
source.next(42);
source.next('test');
This will print to console:
Observer 1: 42
Observer 2: 42
Observer 1: test
Observer 2: test
See live demo: http://plnkr.co/edit/gWMFMnPlLJVDC1pQi8pH?p=preview
Read more:
http://reactivex.io/intro.html
https://github.com/Reactive-Extensions/RxJS#resources
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md
Be aware that Observable.create() is a very different animal. It takes as a parameter a function that is called every time a new Observer subscribes. That's why it take the newly subscribed Observer as an argument. In this function you can for example call next() method on the Observer to send it some default value that all subscribes need to receive.
So you probably want to use Subject instead of Observable.create().