How can I get the current value from an Observable without subscribing to it? I just want the current value one time and not new values as they are coming in.
You need to use BehaviorSubject,
- BehaviorSubject is similar to ReplaySubject except it only remembers the last publication.
- BehaviorSubject also requires you to provide it a default value of T. This means that all subscribers will receive a value immediately (unless it is already completed).
It will give you the most recent value published by the Observable.
BehaviorSubject provides a getter
property named value
to get the most recent value passed through it.
- In this example the value 'a' is written to the console:
//Declare a Subject, you'll need to provide a default value.
const subject: BehaviorSubject<string> = new BehaviorSubject("a");
USAGE:
console.log(subject.value); // will print the current value
Conceal the Subject and only expose it's value
In case you want to conceal your BehaviorSubject and only expose it's value, let's say from a Service, you can use a getter like this.
export class YourService {
private subject = new BehaviorSubject('random');
public get subjectValue() {
return this.subject.value;
}
}
myState:EventEmitter<string> = new EventEmitter();
, so I must use BehaviorSubject instead of EventEmitter, right? Something like myState:BehaviorSubject<string> = new BehaviorSubject('default state');
–
Earthaearthborn BehaviorSubject<T>
into Observable<T>
and only expose that observable to others. Would it be fine with you? –
Intoxicant Quick answer:
...I just want the current value one time and not new values as they are coming in...
You will still use subscribe
, but with pipe(take(1))
so it gives you one single value.
eg. myObs$.pipe(take(1)).subscribe(value => alert(value));
Also see: Comparison between first()
, take(1)
or single()
Longer answer:
The general rule is you should only ever get a value out of an observable with subscribe()
(or async pipe if using Angular)
BehaviorSubject
definitely has its place, and when I started with RxJS I used to often do bs.value()
to get a value out. As your RxJS streams propagate throughout your whole application (and that's what you want!) then it will become harder and harder to do this. Often you'll actually see .asObservable()
used to 'hide' the underlying type to prevent someone from using .value()
- and at first this will seem mean, but you'll start to appreciate why it's done over time. In addition you'll sooner or later need a value of something that isn't a BehaviorSubject
and there won't be a way to make it so.
Back to the original question though. Especially if you don't want to 'cheat' by using a BehaviorSubject
.
The better approach is always to use subscribe
to get a value out.
obs$.pipe(take(1)).subscribe(value => { ....... })
OR
obs$.pipe(first()).subscribe(value => { ....... })
The difference between these two being first()
will error if the stream has already completed and take(1)
will not emit any observables if the stream has completed or doesn't have a value immediately available.
Note: This is considered better practice even if you are are using a BehaviorSubject.
However, if you try the above code the observable's 'value' will be 'stuck' inside the subscribe function's closure and you may well need it in the current scope. One way around this if you really have to is this:
const obsValue = undefined;
const sub = obs$.pipe(take(1)).subscribe(value => obsValue = value);
sub.unsubscribe();
// we will only have a value here if it was IMMEDIATELY available
alert(obsValue);
Important to note that the subscribe call above doesn't wait for a value. If nothing is available right away then the subscribe function won't ever get called, and I put the unsubscribe call there deliberately to prevent it 'appearing later'.
So not only does this look remarkably clumsy - it won't work for something that isn't immediately available, like a result value from an http call, but it would in fact work with a behavior subject (or more importantly something that is upstream and known to be a BehaviorSubject*, or a combineLatest
that takes two BehaviorSubject
values). And definitely don't go doing (obs$ as BehaviorSubject)
- ugh!
This previous example is still considered a bad practice in general - it's a mess. I only do the previous code style if I want to see if a value is available immediately and be able to detect if it isn't.
Best approach
You're far better off if you can to keep everything as an observable as long as possible - and only subscribe when you absolutely need the value - and not try to 'extract' a value into a containing scope which is what I'm doing above.
eg. Lets' say we want to make a report of our animals, if your zoo is open. You might think you want the 'extracted' value of zooOpen$
like this:
Bad way
zooOpen$: Observable<boolean> = of(true); // is the zoo open today?
bear$: Observable<string> = of('Beary');
lion$: Observable<string> = of('Liony');
runZooReport() {
// we want to know if zoo is open!
// this uses the approach described above
const zooOpen: boolean = undefined;
const sub = this.zooOpen$.subscribe(open => zooOpen = open);
sub.unsubscribe();
// 'zooOpen' is just a regular boolean now
if (zooOpen)
{
// now take the animals, combine them and subscribe to it
combineLatest(this.bear$, this.lion$).subscribe(([bear, lion]) => {
alert('Welcome to the zoo! Today we have a bear called ' + bear + ' and a lion called ' + lion);
});
}
else
{
alert('Sorry zoo is closed today!');
}
}
So why is this SO BAD
- What if
zooOpen$
comes from a webservice? How will the previous example ever work? It actually wouldn't matter how fast your server is - you'd never get a value with the above code ifzooOpen$
was an http observable! - What if you want to use this report 'outside' this function. You've now locked away the
alert
into this method. If you have to use the report elsewhere you'd have to duplicate this!
Good way
Instead of trying to access the value in your function, consider instead a function that creates a new Observable and doesn't even subscribe to it!
It instead returns a new observable that can be consumed 'outside'.
By keeping everything as observables and using switchMap
to make decisions you can create new observables that can themselves be the source of other observables.
getZooReport() {
return this.zooOpen$.pipe(switchMap(zooOpen => {
if (zooOpen) {
return combineLatest(this.bear$, this.lion$).pipe(map(([bear, lion] => {
// this is inside 'map' so return a regular string
return "Welcome to the zoo! Today we have a bear called ' + bear + ' and a lion called ' + lion";
}
);
}
else {
// this is inside 'switchMap' so *must* return an observable
return of('Sorry the zoo is closed today!');
}
});
}
The above creates a new observable so we can run it elsewhere, and pipe it more if we wish.
const zooReport$ = this.getZooReport();
zooReport$.pipe(take(1)).subscribe(report => {
alert('Todays report: ' + report);
});
// or take it and put it into a new pipe
const zooReportUpperCase$ = zooReport$.pipe(map(report => report.toUpperCase()));
Note the following:
- I don't subscribe until I absolutely need to - in this case that's outside the function
- The 'driving' observable is
zooOpen$
and that usesswitchMap
to 'switch' to a different observable which is ultimately the one returned fromgetZooReport()
. - The way this works if
zooOpen$
ever changes then it cancels everything and starts again inside the firstswitchMap
. Read up aboutswitchMap
for more about that. - Note: The code inside
switchMap
must return a new observable. You can make one quickly withof('hello')
- or return another observable such ascombineLatest
. - Likewise:
map
must just returns a regular string.
As soon I started making a mental note not to subscribe until I had to I suddenly started writing much more productive, flexible, cleaner and maintainable code.
Another final note: If you use this approach with Angular you could have the above zoo report without a single subscribe
by using the | async
pipe. This is a great example of the 'don't subscribe until you HAVE to' principal in practice.
// in your angular .ts file for a component
const zooReport$ = this.getZooReport();
and in your template:
<pre> {{ zooReport$ | async }} </pre>
See also my answer here:
https://mcmap.net/q/98071/-how-to-get-current-value-of-rxjs-subject-or-observable
Also not mentioned above to avoid confusion:
tap()
may be useful sometimes to 'get the value out of an observable'. If you aren't familiar with that operator read into it. RxJS uses 'pipes' and atap()
operator is a way to 'tap into the pipe' to see what's there.
With .toPromise() / async
See 'Use toPromise() with async/await to emit the last Observable value as a Promise' in https://benlesh.medium.com/rxjs-observable-interop-with-promises-and-async-await-bebb05306875
With Angular signals! (Angular 16+)
Angular 16 introduced a preview of a new Signals feature. While not feature complete you can already start use signals in your components. One of the design goals is precisely to fix this 'pain point'.
With customer = toSignal(customer$, { requireSync: true })
you can create a signal that updates when the observable value changes and access the 'immediate' value with customer()
without the normal boilerplate described above. Angular automatically handles the unsubscription to the observable that is created in order to achieve this.
Thoughts on this:
- Accessing a signal with
()
to get its value is always synchronous and so if your observable doesn't have a value you'll run into some of the same problems described above. That's what{ requireSync: true }
is for (check API docs for more info - subject to change). - I'm not suggesting creating a new signal at time of needing the value of an existing observable. That's sort of pointless! This approach is only going to be useful if you're updating your component to use signals either in part or fully.
- Signal APIs are not final so check for blogs for the latest information.
take(1)
then unsubscribing wouldn't ever be needed. But generally this approach should be avoided - unless you know 100% that a value is immediately available. –
Bedraggled await
in asynchronous functions. indepth.dev/rxjs-for-await-what –
Bedraggled bs$.value
in my code. I always ended up getting in a mess in the end. At the simplest level your code might work with a behavior subject today but then tomorrow you need to combine it with some other input or data and then suddenly you can't use .value
anymore. It works, but feels clumsy and I never use it anymore. –
Bedraggled You need to use BehaviorSubject,
- BehaviorSubject is similar to ReplaySubject except it only remembers the last publication.
- BehaviorSubject also requires you to provide it a default value of T. This means that all subscribers will receive a value immediately (unless it is already completed).
It will give you the most recent value published by the Observable.
BehaviorSubject provides a getter
property named value
to get the most recent value passed through it.
- In this example the value 'a' is written to the console:
//Declare a Subject, you'll need to provide a default value.
const subject: BehaviorSubject<string> = new BehaviorSubject("a");
USAGE:
console.log(subject.value); // will print the current value
Conceal the Subject and only expose it's value
In case you want to conceal your BehaviorSubject and only expose it's value, let's say from a Service, you can use a getter like this.
export class YourService {
private subject = new BehaviorSubject('random');
public get subjectValue() {
return this.subject.value;
}
}
myState:EventEmitter<string> = new EventEmitter();
, so I must use BehaviorSubject instead of EventEmitter, right? Something like myState:BehaviorSubject<string> = new BehaviorSubject('default state');
–
Earthaearthborn BehaviorSubject<T>
into Observable<T>
and only expose that observable to others. Would it be fine with you? –
Intoxicant const value = await this.observableMethod().toPromise();
Use toPromise() with async/await to emit the last Observable value as a Promise
–
Bedraggled As ToPromise() is deprecated from some time here is the new rxjs way to do this:
let value = await lastValueFrom(someObservable);
or
let value = await firstValueFrom(someObservable);
If you are not sure that something is inside already you can use:
let value = await lastValueFrom(someObservable, {defaultValue: "some value"})
https://indepth.dev/posts/1287/rxjs-heads-up-topromise-is-being-deprecated
Not sure if this is what you are looking for. Probably write that behaviorsubject in a service. Declare it as a private and expose only the value that you have set. Something like this
@Injectable({
providedIn: 'root'
})
export class ConfigService {
constructor(private bname:BehaviorSubject<String>){
this.bname = new BehaviorSubject<String>("currentvalue");
}
getAsObservable(){
this.bname.asObservable();
}
}
This way the external users only have access to subscribe to the behaviorSubject and you get to set the desired value in the service.
Use the Observable constructor to create an observable stream of any type. The constructor takes as its argument the subscriber function to run when the observable’s subscribe() method executes. A subscriber function receives an Observer object, and can publish values to the observer's next() method.Try this
@Component({
selector: 'async-observable-pipe',
template: '<div><code>observable|async</code>: Time: {{ time | async }} .
</div>'
})
export class AsyncObservablePipeComponent {
time = new Observable<string>((observer: Observer<string>) => {
setInterval(() => observer.next(new Date().toString()), 1000);
});
}
© 2022 - 2024 — McMap. All rights reserved.
.map
directly while using http request ? – Elihu