How to subscribe to observable multiple times? - rxjs

I have a table that needs to be refreshed each time a user delete a row, or edit data with a designate function called reloadUsersData();
I've created a BehaviorSubject that gets the table's data from a service - once the table is loaded - but i'm not sure that my implementation of reloadUsersData is good since i repeat the same code and resubscribe to the same observable ....
export class TableBasicExample implements OnDestroy, OnInit{
dataSource$ = new BehaviorSubject<any>([]);
private subs: Subscription[] = [];
constructor(private apiService: ApiService) {}
ngOnInit() {
this.subs.push(
this.apiService.getUsers().subscribe((res: any) => {
this.dataSource$.next(res);
}));
}
reloadUsersData(){
// how can i subscribe to the same api more effectively ?
this.subs.push(
this.apiService.getUsers().subscribe((res: any) => {
this.dataSource$.next(res);
}));
}
ngOnDestroy(): void {
this.subs.forEach((us) => us.unsubscribe());
}
}

Fortunately, this can be simplified! I'm assuming that .getUsers() emits one array of users and completes. With that in mind, you have the right idea with creating a subject to handle reload events.
One strategy you can use in RxJS is to create an observable that includes a subject in its .pipe(). This is because a Subject inherits all capabilities of an Observable.
Here's the code.
export class TableBasicExample {
private fetchEvent = new BehaviorSubject<'fetch'>('fetch');
public users$: Observable<User[]>;
constructor(private apiService: ApiService) {
this.users$ = this.fetchEvent.pipe(
switchMapTo(this.apiService.getUsers())
);
}
reloadUsersData() {
this.fetchEvent.next('fetch');
}
}
We create a BehaviorSubject (which is an extension of Subject) to handle our fetch event. We have it emit the string 'fetch', but it could honestly be any value.
Next, we declare the main Observable we use for our table rendering. It will subscribe to fetchEvent and will switchMap to an inner observable (.getUsers()) and emit its value.
Last, we have our public method that will emit a new value in our private BehaviorSubject.
In your component's template file, you can subscribe to this observable using the async pipe.
<table *ngIf="users$ | async as users">
<tr *ngFor="user of users">
<!-- table row data -->
</tr>
</table>
<button (click)="reloadUsersData()">Reload</button>
The async pipe handles the subscribe/unsubscribe events for you, so you no longer need onInit and onDestroy in your component's TS file.
And that's it! When reloadUsersData() is invoked, it causes fetchEvent to emit a new value. Because users$ has an active subscription, it will receive that new value, and again subscribe to the inner switchMap observable.
Note: The switchMapTo(obs$) operator is similar to switchMap((value)=>$obs) operator. It's just a little less code because we don't actually need the value from our BehaviorSubject.

Related

Subject-like RxJS Observable that transparently pipes through flatMap

When using Dependency injection in Angular I often need to subscribe to an observable that I haven't yet created!
I often end up using something like this:
// create behavior subject OF Observable<number>
const subject = new BehaviorSubject<Observable<number>>(EMPTY);
// subscribe to it, using flatMap such as to 'unwrap' the observable stream
const unwrappedSubject = subject.pipe(flatMap((x: number) => x));
unwrappedSubject.subscribe(s => console.log(s));
// now actually create the observable stream
const tim = timer(1000, 1000);
// set it into the subject
subject.next(tim);
This uses flatMap to 'unwrap' the observable contained in the subject.
This works fine, but frankly it always feels 'icky'.
What I really want is something like this, where the consumer of the subject treats the instance of the Subject as Observable<number> without having to pipe it every usage.
const subject = new UnwrappingBehaviorSubject<number>(EMPTY);
subject.subscribe((x: number) => console.log(x));
// this could use 'next', but that doesn't feel quite right
subject.setSource(timer(1000, 1000));
I'm aware that I could subscribe to the timer and hook it up directly to the subject, but I also want to avoid an explicit subscribe call because that complicates the responsibility of unsubscribing.
timer(1000, 1000).subscribe(subject);
Is there a nice way to achieve this?
The Subject.ts and BehaviorSubject.ts source files get more complicated than I expected. I'm scared I'll end up with horrible memory leaks if I try to fork it.
I think this would be another way to solve it:
foo.component.ts
export class FooComponent {
private futureObservable$ = new Observable(subscriber => {
// 'Saving' the subscriber for when the observable is ready.
this.futureObservableSubscriber = subscriber;
// The returned function will be invoked when the below mentioned subject instance
// won't have any subscribers(after it had at least one).
return () => this.futureObservableSubscription.unsubscribe();
}).pipe(
// You can mimic the Subject behavior from your initial solution with the
// help of the `share` operator. What it essentially does it to *place*
// a Subject instance here and if multiple subscriptions occur, this Subject instance
// will keep track of all of them.
// Also, when the first subscriber is registered, the observable source(the Observable constructor's callback)
// will be invoked.
share()
);
private futureObservableSubscriber = null;
// We're using a subscription so that it's easier to collect subscriptions to this observable.
// It's also easier to unsubscribe from all of them at once.
private futureObservableSubscription = new Subscription();
constructor (/* ... */) {};
ngOnInit () {
// If you're using `share`, you're safe to have multiple subscribers.
// Otherwise, the Observable's callback(i.e `subscriber => {...}`) will be called multiple times.
futureObservable$.subscribe(/* ... */);
futureObservable$.subscribe(/* ... */);
}
whenObservableReady () {
const tim = timer(1000, 1000);
// Here we're adding the subscription so that is unsubscribed when the main observable
// is unsubscribed. This part can be found in the returned function from the Observable's callback.
this.futureObservableSubscription.add(tim.subscribe(this.futureObservableSubscriber));
}
};
Indeed, a possible downside is that you'll have to explicitly subscribe, e.g in the whenObservableReady method.
With this approach you can also have different sources:
whenAnotherObservableReady () {
// If you omit this, it should mean that you will have multiple sources at the same time.
this.cleanUpCrtSubscription();
const tim2 = timer(5000, 5000);
this.futureObservableSubscription.add(tim2.subscribe(this.futureObservableSubscriber));
}
private cleanUpCrtSubscription () {
// Removing the subscription created from the current observable(`tim`).
this.futureObservableSubscription.unsubscribe();
this.futureObservableSubscription = new Subscription();
}

RxJS 6: Why calling value on BehaviorSubject is a bad thing? (according to no-subject-value lint rule) [duplicate]

I have an Angular 2 service:
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
}
}
Everything works great. But I have another component which doesn't need to subscribe, it just needs to get the current value of isLoggedIn at a certain point in time. How can I do this?
A Subject or Observable doesn't have a current value. When a value is emitted, it is passed to subscribers and the Observable is done with it.
If you want to have a current value, use BehaviorSubject which is designed for exactly that purpose. BehaviorSubject keeps the last emitted value and emits it immediately to new subscribers.
It also has a method getValue() to get the current value.
The only way you should be getting values "out of" an Observable/Subject is with subscribe!
If you're using getValue() you're doing something imperative in declarative paradigm. It's there as an escape hatch, but 99.9% of the time you should NOT use getValue(). There are a few interesting things that getValue() will do: It will throw an error if the subject has been unsubscribed, it will prevent you from getting a value if the subject is dead because it's errored, etc. But, again, it's there as an escape hatch for rare circumstances.
There are several ways of getting the latest value from a Subject or Observable in a "Rx-y" way:
Using BehaviorSubject: But actually subscribing to it. When you first subscribe to BehaviorSubject it will synchronously send the previous value it received or was initialized with.
Using a ReplaySubject(N): This will cache N values and replay them to new subscribers.
A.withLatestFrom(B): Use this operator to get the most recent value from observable B when observable A emits. Will give you both values in an array [a, b].
A.combineLatest(B): Use this operator to get the most recent values from A and B every time either A or B emits. Will give you both values in an array.
shareReplay(): Makes an Observable multicast through a ReplaySubject, but allows you to retry the observable on error. (Basically it gives you that promise-y caching behavior).
publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), etc: Other operators that leverage BehaviorSubject and ReplaySubject. Different flavors of the same thing, they basically multicast the source observable by funneling all notifications through a subject. You need to call connect() to subscribe to the source with the subject.
I had similar situation where late subscribers subscribe to the Subject after its value arrived.
I found ReplaySubject which is similar to BehaviorSubject works like a charm in this case.
And here is a link to better explanation: http://reactivex.io/rxjs/manual/overview.html#replaysubject
const observable = of('response')
function hasValue(value: any) {
return value !== null && value !== undefined;
}
function getValue<T>(observable: Observable<T>): Promise<T> {
return observable
.pipe(
filter(hasValue),
first()
)
.toPromise();
}
const result = await getValue(observable)
// Do the logic with the result
// .................
// .................
// .................
You can check the full article on how to implement it from here.
https://www.imkrish.com/blog/development/simple-way-get-value-from-observable
I encountered the same problem in child components where initially it would have to have the current value of the Subject, then subscribe to the Subject to listen to changes. I just maintain the current value in the Service so it is available for components to access, e.g. :
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
isLoggedIn: boolean;
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
this.currIsLoggedIn = false;
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
this.isLoggedIn = value;
}
}
A component that needs the current value could just then access it from the service, i.e,:
sessionStorage.isLoggedIn
Not sure if this is the right practice :)
A similar looking answer was downvoted. But I think I can justify what I'm suggesting here for limited cases.
While it's true that an observable doesn't have a current value, very often it will have an immediately available value. For example with redux / flux / akita stores you may request data from a central store, based on a number of observables and that value will generally be immediately available.
If this is the case then when you subscribe, the value will come back immediately.
So let's say you had a call to a service, and on completion you want to get the latest value of something from your store, that potentially might not emit:
You might try to do this (and you should as much as possible keep things 'inside pipes'):
serviceCallResponse$.pipe(withLatestFrom(store$.select(x => x.customer)))
.subscribe(([ serviceCallResponse, customer] => {
// we have serviceCallResponse and customer
});
The problem with this is that it will block until the secondary observable emits a value, which potentially could be never.
I found myself recently needing to evaluate an observable only if a value was immediately available, and more importantly I needed to be able to detect if it wasn't. I ended up doing this:
serviceCallResponse$.pipe()
.subscribe(serviceCallResponse => {
// immediately try to subscribe to get the 'available' value
// note: immediately unsubscribe afterward to 'cancel' if needed
let customer = undefined;
// whatever the secondary observable is
const secondary$ = store$.select(x => x.customer);
// subscribe to it, and assign to closure scope
sub = secondary$.pipe(take(1)).subscribe(_customer => customer = _customer);
sub.unsubscribe();
// if there's a delay or customer isn't available the value won't have been set before we get here
if (customer === undefined)
{
// handle, or ignore as needed
return throwError('Customer was not immediately available');
}
});
Note that for all of the above I'm using subscribe to get the value (as #Ben discusses). Not using a .value property, even if I had a BehaviorSubject.
Although it may sound overkill, this is just another "possible" solution to keep Observable type and reduce boilerplate...
You could always create an extension getter to get the current value of an Observable.
To do this you would need to extend the Observable<T> interface in a global.d.ts typings declaration file. Then implement the extension getter in a observable.extension.ts file and finally include both typings and extension file to your application.
You can refer to this StackOverflow Answer to know how to include the extensions into your Angular application.
// global.d.ts
declare module 'rxjs' {
interface Observable<T> {
/**
* _Extension Method_ - Returns current value of an Observable.
* Value is retrieved using _first()_ operator to avoid the need to unsubscribe.
*/
value: Observable<T>;
}
}
// observable.extension.ts
Object.defineProperty(Observable.prototype, 'value', {
get <T>(this: Observable<T>): Observable<T> {
return this.pipe(
filter(value => value !== null && value !== undefined),
first());
},
});
// using the extension getter example
this.myObservable$.value
.subscribe(value => {
// whatever code you need...
});
There are two ways you can achieve this.
BehaviorSubject has a method getValue() which you can get the value in a specific point of time.
You can subscribe directly with the BehaviorSubject and you may pass the subscribed value to a class member, field or property.
I wouldn't recommend both approaches.
In the first approach, it's a convenient method you can get the value anytime, you may refer to this as the current snapshot at that point of time. Problem with this is you can introduce race conditions in your code, you may invoke this method in many different places and in different timing which is hard to debug.
The second approach is what most developers employ when they want a raw value upon subscription, you can track the subscription and when do you exactly unsubscribe to avoid further memory leak, you may use this if you're really desperate to bind it to a variable and there's no other ways to interface it.
I would recommend, looking again at your use cases, where do you use it? For example you want to determine if the user is logged in or not when you call any API, you can combine it other observables:
const data$ = apiRequestCall$().pipe(
// Latest snapshot from BehaviorSubject.
withLatestFrom(isLoggedIn),
// Allow call only if logged in.
filter(([request, loggedIn]) => loggedIn)
// Do something else..
);
With this, you may use it directly to the UI by piping data$ | async in case of angular.
A subscription can be created, then after taking the first emitted item, destroyed. In the example below, pipe() is a function that uses an Observable as its input and returns another Observable as its output, while not modifying the first observable.
Sample created with Angular 8.1.0 packages "rxjs": "6.5.3", "rxjs-observable": "0.0.7"
ngOnInit() {
...
// If loading with previously saved value
if (this.controlValue) {
// Take says once you have 1, then close the subscription
this.selectList.pipe(take(1)).subscribe(x => {
let opt = x.find(y => y.value === this.controlValue);
this.updateValue(opt);
});
}
}
You could store the last emitted value separately from the Observable. Then read it when needed.
let lastValue: number;
const subscription = new Service().start();
subscription
.subscribe((data) => {
lastValue = data;
}
);
The best way to do this is using Behaviur Subject, here is an example:
var sub = new rxjs.BehaviorSubject([0, 1])
sub.next([2, 3])
setTimeout(() => {sub.next([4, 5])}, 1500)
sub.subscribe(a => console.log(a)) //2, 3 (current value) -> wait 2 sec -> 4, 5
Another approach, If you want / can to use async await (has to be inside of an async functions) you can do this with modern Rxjs:
async myFunction () {
const currentValue = await firstValueFrom(
of(0).pipe(
withLatestFrom(this.yourObservable$),
map((tuple) => tuple[1]),
take(1)
)
);
// do stuff with current value
}
This will emit a value "Right away" because of withLatestFrom, and then will resolve the promise.

RxJs Observable duplicate values

So i have pretty straight forward scenario. One subject and observable. When client logs in i publish success, when user logs out i publish false.
Problem is in subscribe method in LoginComponent
First time everything works great. User logs in i get one event, but after that when user logs out second time and logs in again i get 2 same events, again if user logs out and then logs in i get 3 duplicate events and so on.
AuthService.ts
public _loggedIn: Subject<LoggedInOrResetPassword> = new Subject();
public loggedId: Observable<LoggedInOrResetPassword> = this._loggedIn.asObservable();
obtainAccessToken(){
// ommitted
this.httpClient.post(environment.baseUrl + url, null, requestOptions)
.subscribe(data => {
this.saveToken(data);
this._loggedIn.next(LoggedInOrResetPassword.createTrue());
});
// ommitted
}
private logout(navigateTo?: string){
this._loggedIn.next(LoggedInOrResetPassword.createFalse());
}
LoginComponent.ts
ngOnInit() {
this.authservice.loggedId.subscribe( ( loggedInOrResetPassword: LoggedInOrResetPassword ) => {
// HERE I GET DUPLICATE VALUES
});
The reason is that you are NOT unsubscribing when LoginComponent is destroyed.
Your code should be changed as follows
First add an instance property to LoginComponent to store the subscription, such as
export class LoginComponent implements OnInit, OnDestroy {
.....
loginSubscription: Subscription;
.....
}
Then change ngOnInit so that you store the subscription in the newly added property
ngOnInit() {
this.loginSubscription = this.authservice.loggedId.subscribe( ( loggedInOrResetPassword: LoggedInOrResetPassword ) => {
// HERE I GET DUPLICATE VALUES
});
Eventually add ngOnDestroy to make sure you unsubscribe when the component gets destroyed
ngOnDestroy {
if (this.loginSubscription) {
this.loginSubscription.unsubscribe();
}
}
Take a look at the async pipe of Angular as an alternative method to subscribe to Observables and automatically unsubscribe.

How to create an observable?

I think I understand the concept of Observables in Rxjs but I am struggling to convert the theory into code.
I have an Angular component, say C. I want that it should show certain HTML components depending on whether the user is logged in or not. For this I am using the code similar to
<ng-container *ngIf = "userNotloggedIn">
...
I have an Angular Service which authenticates the user. I want that once the user is authenticated, the service should emit a value (say boolean true or false corresponding to signin and sign out). I will like to use an Observable for this and my component should subscribe to this Observable.
I am struggling to convert this into code. I my service, I have created an Observable as follows and then I emit the values (true or false) when the user signs in or signs out:
export class UserManagementService {
userSignedIn$: Observable<boolean>;
constructor(private http: HttpClient, private bs:WebToBackendInterfaceService) {
this.userSignedIn$ = Rx.Observable.of(false)
}
onUserSignout(){
console.log("In UserManagementService: onUserSignout")
return this.bs.onUserSignout().subscribe((res/*:ServerResponse*/)=>{
console.log("response fromm server",res)
this.userSignedIn$ = Rx.Observable.of(false)
}); //res is of type HttpResponse
}
signinUser(user:UserSigninInfo){
return this.bs.signinUser(user).subscribe((res:HttpResponse<any>)=>{console.log('response from server:',res);
console.log('response headers',res.headers.keys())
this.userSignedIn$ = Rx.Observable.of(true)
} )
}
Then in the component, I subscribe to the observable as follows:
ngOnInit(){
this.userloggedIn = false;
this.userManagementService.userSignedIn$.subscribe(x=>{console.log("got stream value ",x);
this.userloggedIn = x})
...
}
The above code doesn't work though. I get the very first value emitted by the Observable (corresponding to this.userManagementService.userSignedIn$.subscribe(x=>{console.log("got stream value ",x);) but nothing after that.
I suspect that it could be because I subscribe to the initial Observable but not to the new ones created in the signin and signout functions. How can I solve my problem?
Observable.of(true) returns a cold observable which cannot emit new values over time.
use Behavior Subject for userSignedIn$ instead
userSignedIn$=new BehabviorSubject();
and when you want to update the signin status use
userSignedIn$.next(true)

Why don't we need to subscribe to an observable here before converting it to a Promise?

My Service class contains code as :
Service.ts
//all imports are done
#Injectable()
export class Service{
constructor(private http: Http) { }
getGoogle():Observable<any> {
console.log("Inside service");
return this.http.get('https://jsonplaceholder.typicode.com/posts/1');
}
}
My page component where I make use of service.
Page.ts
//all imports are done
export class Page1{
constructor(private service: Service, private navCtrl: NavController) { }
async get() {
console.log("inside get method");
const data = await this.service.getGoogle().toPromise();
console.log('The response is' , data);
}
}
I have got the required result, but as to understand the concept of Observables and Observer, my Observable should have an Observer sitting to subscribe.Then why should'nt the code const data = await this.service.getGoogle.subscribe().toPromise() does not work here and shows error that property toPromise() does not exists on type Subscription.
I saw the official resource of toPromise() where I found that it is used with .just().toPromise().Then I found .just() API which states that
The just() method emits its parameter(s) as OnNext notifications, and
after that, it emits an OnCompleted notification.
So It is using the features of subscribe here then why it is not used with .subscribe()?
To get the values from an observable, you'll subscribe() on the observable. This starts the observable and it will send you the values it produces.
If you rather want to use a Promise you can call toPromise() on the observable instead. This will make the observable behave like a regular promise.
Under the covers, toPromise() calls subscribe() on the observable and waits for it to send complete(). When the complete() signal is received, the promise will resolve the last emitted next() signal.
toPromise() looks a bit like this:
myObservable.takeLast().subscribe(value => resolve(value));

Resources