Reputation: 2755
This question looks long, but it should be a very basic rxjs question. Please help if you can.
I'm writing an Angular 7 app, with a service, component and a template as follows, and I want to access the last value of the observable in an easy way.
SoundMeterService
In the service, I have a private BehaviorSubject which I use to emit values using 'next'.
I expose this BehaviorSubject to consumers using .asObservable() in order to prevent consumers from doing .next() and emit new values.
In the service, I access the current value of listeningStatusSubject
using .value.
export type SoundMeterStatus = 'noResults' | 'isRunning' | 'hasResults';
export class SoundMeterService {
private listeningStatusSubject: BehaviorSubject<SoundMeterStatus> = new BehaviorSubject<SoundMeterStatus>('noResults');
get listeningStatus() { return this.listeningStatusSubject.asObservable(); }
// somewhere in the code I emit a different value
someFunc() {
if (this.listeningStatusSubject.value === 'noResults' )
this.listeningStatusSubject.next('isRunning');
}
}
SoundMeterComponent
Following best practices with observables, in the component, I'm using direct access to the service's Observable (via listeningStatus
getter) so that it is automatically subscribed to and unsubscribed in the template (see below).
export class SoundMeterComponent {
serviceListeningStatus$ = this.sms.listeningStatus;
constructor(
private sms: SoundMeterService
) {}
ngOnInit() {
// I want to check the value of listeningStatus in a simple way without pulling the value out and saving it locally in SoundMeterComponent
}
}
SoundMeterTemplate
I'm using Angular's async pipe so that it automatically subscribes and unsubscribes to the observable when the component is destroyed (to avoid manual subscriptions as mentioned here)
<div *ngIf="(serviceListeningStatus | async) === 'noResults' ">
<span>some text</span>
</div>
My question
This approach works perfectly. But I want to check the value of listeningStatus
in a simple way inside ngOnInit() without pulling the value out and saving it locally in SoundMeterComponent, and without exposing the BehaviorSubject listeningStatusSubject
itself to check its value using .value, as described below:
1) Pulling the value from the service and saving it locally in SoundMeterComponent:
SoundMeterComponent
export class SoundMeterComponent {
serviceListeningStatus$ = this.sms.listeningStatus;
private serviceListeningStatus;
constructor(
private sms: SoundMeterService
) {}
ngOnInit() {
// Pulling the value from the service:
this.sms.listeningStatus.subscribe( status => this.serviceListeningStatus = status ); // I'm trying to avoid this
if (this.serviceListeningStatus === 'noResults' ) {
displayNoResultsMessage();
}
}
}
2) exposing the BehaviorSubject itself and check its value using .value like this:
SoundMeterService
export class SoundMeterService {
private listeningStatus: BehaviorSubject<SoundMeterStatus> = new BehaviorSubject<SoundMeterStatus>('noResults');
get listeningStatus() { return this.listeningStatus; }
}
SoundMeterComponent
export class SoundMeterComponent {
serviceListeningStatus = this.sms.listeningStatus;
constructor(
private sms: SoundMeterService
) {}
ngOnInit() {
if (this.sms.listeningStatus === 'noResults' ) {
displayNoResultsMessage();
}
}
}
SoundMeterTemplate
<div *ngIf="(serviceListeningStatus.asObservable() | async) === 'noResults' ">
<span>some text</span>
</div>
Please help me understand which approach is better while keeping clean and simple code. I know there are many questions on how to get the value from a BehaviorSubject like Simple way to get the current value of a BehaviorSubject with rxjs5 or How to get current value of RxJS Subject or Observable? , but please note that this is a different question. Thanks!
Upvotes: 2
Views: 3651
Reputation: 1783
As far as I can understand your problem I would propably question the usage of a BehaviourSubject
in the first place.
RxJs, as great as it is, is not a fit for every use case and yours could be one of them.
The usage of .value
is an indication for that imo.
Using a simple non Observable variable in the SoundMeterService
that keeps the state would propably resolve all your problems.
If you don't want to do that, my suggestions would be to try something like the following:
There a several places in your flow where you want to check the listeningStatus
and
it may is an option for you to use the tap operator and perform the side effects for all options (not only for 'noResults') in one place?
Example:
export class SoundMeterComponent {
serviceListeningStatus$ = this.sms.listeningStatus.pipe(
tap((status) => {
if (value === 'noResults') {
displayNoResultsMessage();
}
if (value === 'xxx') {
doXXXStuff();
}
})
);
constructor(private sms: SoundMeterService) {}
ngOnInit() {}
}
and the template then subscribes to the serviceListeningStatus$
:
<div *ngIf="(serviceListeningStatus$ | async) === 'noResults' ">
<span>some text</span>
</div>
I think your problem is a great example for an application that wants to do RxJs, because it is great, but due the lack of a full support of reactive approaches in the Angular Framework you can't go fully reactive. I'm thinking of the ngOnInit
or of click events a user makes.
If you want to use RxJs nonetheless it may help to take look at ngx-template-stream or at this great proposal.
Cheers Chris
Upvotes: 1
Reputation: 1776
I'm afraid you have to subscribe to this.sms.listeningStatus, that's just how observables work. There is no workaround if you want to stick to real asynchronous approach of dealing with data. The only thing you could do is move the subscription somewhere else (for instance to the template using async pipe)
Upvotes: 2
Reputation: 897
Can always use async/await in combination with ngOnInit & converting the observable to a promise, to give you some readability:
async ngOnInit = () => {
const currentValue = await this.sms.listeningStatus.pipe(take(1)).toPromise();
// do stuff with currentValue here
}
(note: always use take(1) when converting hot observables to promises - or they'll never resolve, as you need the observable to complete before the promise will resolve).
Upvotes: 2