Best way to chain observable subscriptions in Angular? - javascript

I have always nested subscriptions when I need to call a resource after getting the result of another one, like so:
this.paymentService.getPayment(this.currentUser.uid, this.code)
.valueChanges()
.subscribe(payment => {
this.payment = payment;
this.gymService.getGym(this.payment.gym)
.valueChanges()
.subscribe(gym => {
this.gym = gym;
});
});
I am using Angular v6 and AngularFire2.
Both endpoints (getPayment and getGym) return objects. Is there any more elegant way to do this without nesting one call inside another?

There are many resources available online to get an understanding of how this kind of scenarios can be addressed with rxjs.
Usually you end up using switchMap like this
this.paymentService.getPayment(this.currentUser.uid, this.code)
.pipe(
switchMap(payment => this.gymService.getGym(payment.gym))
)
.subscribe(
this.gym = gym;
)
I have skipped on purpose the valueChanges() call. I do not have any idea of what it does, but it does not sound as right in a reactive world.
This is a nice article about switchMap.

Related

Modyfing data in an observable

I have the following observable: messages$: Observable<Message[] | undefined>. Message has 2 fields: id and content, both of which are string.
What I would like to do is to modify messages$ so that a function foo(string) is invoked on the content of each Message.
It doesn't seem difficult at face value but I'm new to observables and unfortunately I got stuck.
I guess solution is simple:
messages$: Observable<Message[] | undefined> = yourSource
.pipe(
map(messages => {
messages.forEach(value => {
value.content = foo(value.content);
});
return messages;
}
)
What you are asking is how can you change your Observable to an observable with sideeffect. You probably don't ever want that (except for simple cases like logging stuff).
Instead what you want to do is subscribe to that Observable and then do your logic in the subscription. That way you're also guaranteed that your logic is only run once (or the number you want) instead of being reliant on something else subscribing to the observable.
messages$.subscribe(({ content }) => { foo(content); });
Be careful of subscription that is not unsubscribed.
Check out this question for a solution to that generic problem:
RXJS - Angular - unsubscribe from Subjects
If i misunderstood your question, and what you really want is an observable that transforms the data, and your foo method is pure (does not modify the inputs or other external data), the solution is different:
const modifiedMessages$ = messages$.pipe(map(({ content }) => foo(content));

Rxjs nested subscribe with multiple inner subscriptions

Original promise based code I'm trying to rewrite:
parentPromise
.then((parentResult) => {
childPromise1
.then(child1Result => child1Handler(parentResult, child1Result));
childPromise2
.then(child1Result => child2Handler(parentResult, child2Result));
childPromise3
.then(child1Result => child3Handler(parentResult, child3Result));
});
I'm trying to figure a way how to avoid the nested subscriptions anti-pattern in the following scenario:
parent$
.pipe(takeUntil(onDestroy$))
.subscribe((parentResult) => {
child1$
.pipe(takeUntil(onDestroy$))
.subscribe(child1Result => child1Handler(parentResult, child1Result));
child2$
.pipe(takeUntil(onDestroy$))
.subscribe(child2Result => child2Handler(parentResult, child2Result));
child3$
.pipe(takeUntil(onDestroy$))
.subscribe(child3Result => child3Handler(parentResult, child3Result));
});
What would be the correct 'RxJS way' to do this?
That seems pretty strange to me. You're creating new subscription for each child every time parentResult arrives. Even though those eventually indeed will be destroyed (assuming onDestroy$ implementation is correct), seems wrong.
You probably want withLatestFrom(parent$) and three separate pipes for each child.
It might look something like:
child1$.pipe(takeUntil(globalDeath$), withLatestFrom(parent$)).subscribe(([childResult, parentResult]) => ...). Not sure if my JS is correct, can't test it at the moment; but the point is: you're getting the latest result from the parent$ every time child1$ fires. Note that you can reverse the direction if necessary (withLatestFrom(child1$)).
You can: 1) pass parent$ through share, and 2) use flatMap three times, something like:
const sharedParent$ = parent$.pipe(share());
sharedParent$.pipe(
flatMap(parentResult => forkJoin(of(parentResult), child1$)),
takeUntil(onDestroy$)),
.subscribe((results) => child1Handler(...results)); // repeat for all children
(If there's more than 2 children, extracting that into a function with child stream and handler as parameters is a good idea).
That's following the original behavior of waiting with subscribing children until parent$ emits. If you don't need that, you can skip flatMap and just forkJoin sharedParent$ and children.
How about using higher order observables? Something like this:
const parentReplay$ = parent$.pipe(shareReplay(1));
of(
[child1$, child1Handler],
[child2$, child2Handler],
[child3$, child3Handler]
).pipe(
mergeMap([child$, handler] => parentReplay$.pipe(
mergeMap(parentResult => child$.pipe(
tap(childResult => handler(parentResult, childResult))
)
)
).subscribe();
If you were using Promises then the corresponding Observables emit only once and then complete.
If this is the case, you can use forkJoin to execute in parallel the child Observables.
So the code could look like
parent$.pipe(
takeUntil(onDestroy$),
// wait for parent$ to emit and then move on
// the following forkJoin executes the child observables in parallel and emit when all children complete - the value emitted is an array with the 3 notifications coming from the child observables
concatMap(parentResult => forkJoin(child1$, child2$, child3$)).pipe(
// map returns both the parent and the children notificiations
map(childrenResults => ({parentResult, childrenResults})
)
).subscribe(
({parentResult, childrenResults}) => {
child1Handler(parentResult, childrenResults[0]);
child1Handler(parentResult, childrenResults[1]);
child1Handler(parentResult, childrenResults[2]);
}
)

One time operation on First subscription of observable

I am using Rxjs. I have one observable and multiple subscription from different sources. I wish to trigger one function only once after getting first subscription to the observable. Is there any way to achieve this?
Not sure if this fits your scenario, but I have dealt with this in the past as well and found it best to conditionally return a different observable after checking for an initialization variable of some kind. Below is a working example of what I mean.
Component wants a list of states from an API
this.statesService.getStates()
.subscribe((states) => this.states = states);
Service wants to only get the states once from the API
private _states: IState[];
getStates(): Observable<IState[]> {
if (!this._states) {
// we don't have states yet, so return an observable using http
// to get them from the API
// then store them locally using tap
return this.http.get<IState[]>('/options/states').pipe(
tap((answer) => {
this._states = answer;
}),
);
} else {
// subsequent calls will just return an observable of the data needed
return of(this._states);
}
}
In the case above, it's easy to return a conditional observable. Hopefully this provides you some ideas on how to handle your conditional (only on the first subscribe) scenario.
You can use publishReplay(1), refCount() operators. It will ensure to evaluate observable only once and share the same result to all subscribers.

Performance of an angular 2 application with Firebase

I have been creating a web application using angular2 with firebase (angularfire2),
I want to know if my development method is optimized or not.
When user select a group, I check if he is already member of the group.
ngOnInit() {
this.af.auth.subscribe(auth => {
if(auth) {
this.userConnected = auth;
}
});
this.router.params.subscribe(params=>{
this.idgroup=params['idgroup'];
});
this._groupService.getGroupById(this.idgroup).subscribe(
(group)=>{
this.group=group;
this.AlreadyPaticipe(this.group.id,this.userConnected.uid),
}
);
}
this method is work, but when I place the function AlreadyPaticipe(this.group.id,this.userConnected.uid) outside getGroupById(this.idgroup).subscribe() ,I get an error group is undefinded ,I now because angular is asynchrone. I don't khow how I can do it?. How I can optimize my code ?,How I can place the function AlreadyPaticipe(this.group.id,this.userConnected.uid) outside getGroupById(this.idgroup).subscribe()
Thanks in advance.
Everything as stream :
Well first, you shouldn't subscribe that much, the best practice is to combine your observables into one and subscribe to it just once, because everytime you subscribe, you need to cleanup when your component is destroyed (not for http, neither ActivatedRoute though) and you end up managing your subscription imperatively (which is not the aim of RXjs). You can find a good article on this topic here.
You must think everything as a stream, all your properties are observables :
this.user$ = this.af.auth.share(); //not sure of the share, I don't know firebase, don't know what it implies...
this.group$ = this.router.params.map(params => params["idgroup"])
.switchMap(groupID => this.groupService.getGroupById(groupID)).share();
// I imagine that AlreadyPaticipe return true or false, but maybe i'm wrong
this.isMemberOfGroup$ = Observable.combineLatest(
this.group$,
this.user$.filter(user => user !== null)
).flatMap(([group, user]) => this.AlreadyPaticipe(groupID, user.uid));
You don't even have to subscribe ! in your template you just need to use the async pipe. for example:
<span>user: {{user$|async}}</span>
<span>group : {{group$|async}}</span>
<span>member of group : {{isMemberOfGroup$|async}}</span>
Or if you don't want to use the pipe, you can combine all those observable and subscribe only once :
this.subscription = Observable.combineLatest(
this.group$,
this.user$,
this.isMemberOfGroup$
).do(([group, user, memberofGroup]) => {
this.group = group;
this.user = user;
this.isMemberofGroup = memberofGroup;
}).subscribe()
in this case, don't forget to this.subscription.unsubscribe() in ngOnDestroy()
there is a very handy tool on rxJS docs (at the bottom of the page) that helps you to choose the right operator for the right behavior.
I don't care about streams, I want it to work, quick n' dirty :
If You don't want to change your code too much, you could use a Resolve guard that will fetch the data before your component is loaded. Take a look at the docs:
In summary, you want to delay rendering the routed component until all necessary data have been fetched.
You need a resolver.

What are RxJS Subject's and the benefits of using them?

I found the rxJS docs define them as
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.
and it goes on to give examples but I'm looking for a basic ELI5 explanation. From my understanding is it helps handle and define items in a sequence. Is that correct?
I think it would be most helpful to me and others to see a simple function with and without defining an rxJS Subject to understand why it's important?
Thanks!
The easiest way to understand it is to think of a Subject as both a producer and a consumer. It's like an open channel where someone can send a message on one end, and any subscribers will receive it on the other end.
+---------------
Sender | => => => => Subscriber
-----------------------+ +-----------
Message => => => => => => => => => => => Subscriber
-----------------------+ +-----------
| => => => => Subscriber
+---------------
In code terms say you have a service with a subject
class MessageService {
private _messages = new Subject<Message>();
get messages: Observable<Message> {
return this._messages.asObservable();
}
sendMessage(message: Message) {
this._messages.next(message);
}
}
Note the messages getter returning the Subject as an Observable. This is not required. The Subject is already an observable, and anybody could subscribe directly to the Subject. But I think the asObservable pattern is used as a way to limit what users can do with it, i.e. so users only use it to subscribe to, and not emit to. We save the emitting for the sendMessage method.
Now with this service in place, we can inject it into different components, and this can be a way for two (or more) arbitrary components to communicate (or just receive arbitrary event notifications).
class ComponentOne {
constructor(private messages: MessageService) {}
onClick() {
this.messages.sendMessage(new Message(..));
}
}
class ComponentTwo {
constructor(private messages: MessageService) {}
ngOnInit() {
this.messages.messages.subscribe((message: Message) => {
this.message = message;
});
}
}
Angular's own EventEmitter is actually a Subject. When we subscribe to the EventEmitter, we are subscribing to a Subject, and when we emit on the EventEmitter, we are sending a message through the Subject for all subscribers.
See also:
Subject vs BehaviorSubject vs ReplaySubject in Angular
Subjects are useful when the code you're in is the one that is actually originating the observable data. You can easily let your consumers subscribe to the Subject and then call the next() function to push data into the pipeline.
If, however, you are getting data from other source and are just passing it along (perhaps transforming it first), then you most likely want to use one of the creation operators shown here, such as Rx.Observable.fromEvent like so:
var clicks = Rx.Observable.fromEvent(document, 'click');
clicks.subscribe(x => console.log(x));
This allow you to stay in the functional paradigm, whereas using a Subject, while it has its uses, is considered by some to be a smell that you're trying to force imperative code into a declarative framework.
Here is a great answer that explains the difference in the two paradigms.
If you want the most simple explanation ...
Observables are usually the result of something. The result of an http call, and whatever you do with a pipe returns an observable.
But what is the source of those things? Ever wondered how you hook your user events into the whole rxjs thing? The main feature of subjects is that you can call the next() method on them.
When doing reactive programming, the first step is usually to make a list of possible subject you will have.
For instance: lets say we have to make a todo-list app.
We will probably have a couple of variables in our component:
public deleteItem$ = Subject<TodoItem> = new Subject();
public addItem$ = Subject<TodoItem> = new Subject();
public saveList$ = Subject<TodoItem[]> = new Subject();
and in our applicatiuon we will hook these up like this:
<button (click)="deleteItem$.next(item)">Delete</button>
Using rxjs, we will use operators like merge/combineLatest/withLatestFrom to handle these subjects and define our application logic.
I'll see if I can find the time to make a small example.
You can find a study of the semantics of subjects here.
All answered I see are correct. I'll just add that the term subject comes from the observer pattern (cf. https://en.wikipedia.org/wiki/Observer_pattern). As such a subject is sort of a relay, it receives something on one end, and emit it on any of its ends (subscriptions).

Categories

Resources