Javascript rxjs - do something when last value emitted from observable - javascript

Following situation: I make two async http calls.
one for getting a list of names
another for making something for each name in the list/array
For the sake of simplicity: Lets imagine the first call returns me a list of names:
['marta', 'edgar', 'david'].
And the second http call post the names in the database.
My implementation works fine for this operational requirement. It looks as follows:
public deployAllPartners(): void {
this.isDeploying = true;
this.getAllPartner().subscribe(shortname => this.adminService.deploySinglePartnerForTesting(shortname).subscribe());
}
private getAllPartner(): Observable<string> {
return this.partnerService.getPartnersOverview()
.flatMap((partnerList) => partnerList.partner) <== returns an array
.map((partner) => partner.shortname);
}
Problem:
Now what I want is that the boolean isDeploying is turning to false when the last name was deployed. Is there any RxJS Operator which is triggered when the last shortname is deployed? Maybe something like finally() or something along those line?. For completness: The boolean is there for a loading gif in the HTML and the gif is only showing up in the UI when isDeploying=true and of course is hidden when the value is false.

If you want to "end" the chain after a source Observable completes you can use concat or since RxJS 6.2.0 also the new endWith operator as well.
Or if you don't want to append any values and just do some side-effect you can use the complete handler in your subscribe() call.
import { from, of } from 'rxjs';
import { concat } from 'rxjs/operators';
from(['a', 'b', 'c'])
.pipe(
concat(of('end')),
)
.subscribe({
next: console.log,
complete: () => console.log('completed'),
});
See live demo: https://stackblitz.com/edit/rxjs6-demo-tplu6y?file=index.ts

Use finalize for RXJS6:
this.getAllPartner().
.pipe(
finalize(() => this.isDeploying = false)
)

Related

Typescript RXJS Subject await async susbscriptions

Suppose I have two completely independent pieces of code in two completely unrelated classes that subscribe to the same Observable in a service class.
class MyService {
private readonly subject = new Subject<any>();
public observe(): Observable<any> {
return this.subject.pipe();
}
}
class A {
constructor(private readonly service: MyService) {
service.observe().subscribe( async (value) => {
await this.awaitAnOperation(value);
console.log('Class A subscription complete', value);
});
}
}
class B {
constructor(private readonly service: MyService) {
service.observe().subscribe( (value) => console.log('Class B subscription complete', value));
}
}
The issue that I am now facing is that when the service emits an event, the log of class B will come before A, even though A subscribed first. What I need is that all methods are ran and finished before going to the next one. I know if A were to be synchronously than my question would be solved, but A does need to run an async operation AND Bcan only log after A has logged.
A and B are completely unaware of eachother and should be as well. In e.g. C# we can run an async method synchrnonously by using GetAwaiter().Wait(); and is not considered a bad practice since when it needs to run on the main thread. An equivalent TS/JS option would be nice.
EDIT
A subscribes before B. It is simply the chronological order of subscribing that should also execute. I know this is by default emitted in that sequence, but the fact remains that running a subscription method on a different thread would continue the main thread to the next subscription. This is what I need to avoid somehow.
I had a similar issue that I solved with an operator I called forkConcat. Instead of subscribing multiple times, I made multiple operators and chained them so that source$.pipe(operatorA) would happen and complete before source$.pipe(operatorB) started, and that would complete before source$.pipe(operatorC) started, and all three completed before dealing with the next value from source$.
My code looked like this...
source$.pipe(
forkConcat(
concat,
operatorA,
operatorB,
operatorC
) )
where forkConcat is defined as
import { merge, Observable, of } from 'rxjs';
import { concatMap, Operator } from 'rxjs/operators';
/*
Creates an operator that will fork several operators from the same input, and not proceed until all those things are done.
First Argument:
If those things should be done in turn, send concat as the first argument.
If each operator should be done in parallel, use merge (or undefined) as the first argument.
To return an array of each operators' final values per value received by forkConcat, use forkJoin.
You could also use combineLatest, etc.
All other arguments are operators.
*/
type Combiner<T> = (...array$: ObservableInput<T>[]) => Observable<T>;
export function forkConcat<T,R>(combiner: Combiner<T> = merge, ...arrayOperators: Operator<T, R>[]) {
return concatMap<T,R>((x) => {
try {
const x$ = of(x);
const o = arrayOperators
.filter(op => !!op) // ignore falsy arguments
.map(op => x$.pipe(op));
return o.length ? combiner(...o) : x$;
} catch (e) {
throw new ForkConcatError(x, e, combiner, arrayOperators);
}
});
}
class ForkConcatError<T> extends Error {
constructor(
readonly receivedValue: T,
readonly innerError: Error,
readonly combiner: Combiner<T>,
readonly arrayOperators: Operator<T, R>[]
) {
super(innerError.message);
}
}
It worked. But I've also got to tell you...
I threw it away
I slowly began to realize that the need for forkConcat was a sign that I should be doing things differently. I haven't seen your code but when you say they shouldn't know about each other yet one affects the other, I highly suspect you should consider the same. If, instead of global/shared variables, you had global/shared subjects and observables where the one that emitted to B was...
source$.pipe(
concatMap(x => concat(
of(x).pipe(
operatorA,
ignoreElwments()
),
of(x) // B should receive the same value A did.
)
)
... then you wouldn't have this issue and your code would be cleaner.
In my case, I went a different route. I made a State interface, and then instead of passing source values through my operators, I passed {source: Source, state: State} objects. This way, there was no longer any global variables at all! Every operator could be a pure function (or pure function plus side effects) using only the combined value/state pairs emitted into them. State errors were much harder to make! To use this tactic, start thinking of your A and B as operators (that don't know about each other) instead of subscribers (that don't know about each other) and your future self will likely be happier.
But just in case I'm wrong about that advice, you now have forkConcat.
You can do that by combining the two Observables using switchMap rxjs operator. it will be guarantied that the second Observable B will not started unless the first one A is done.
Here a good example to this scenario in the section addressed "Combining Observables in series":
https://blog.danieleghidoli.it/2016/10/22/http-rxjs-observables-angular/

waiting observable subscribe inside foreach with forkJoin

I am trying to populate an array in my component called conventions which is an array of convention.
Each organization has a list of contracts, and each contract has a convention id, with this id i got the convention.
I use getOrganizationForUser to get current organization and then get the list of contract.
Then i use the convention id from contract to call the second API to get the convention.
Currently, my code looks something like this:
public getOrganizationForUser(): Observable<Organization> {
return this.httpClient
.get<Organization>(`${c.serviceBaseUrl.sp}/organizationByUser`)
.pipe(catchError((err, source) => this.responseHandler.onCatch(err, source)));
}
public getById(id: number) {
return this.httpClient
.get<Convention>(`${c.serviceBaseUrl.sp}/conventions/` + id)
.pipe(catchError((err, source) => this.responseHandler.onCatch(err, source)));
}
ngOnInit() {
this.OrganizationService.getOrganizationForUser().subscribe((organization: Organization) => {
organization.contracts.forEach((contract) => {
this.conventionService.getById(contract.conventionId).subscribe((convention: Convention) => {
this.conventions.push(convention);
})
})
})
}
I understand that I can create an array of observables, and use Observable.forkJoin() to wait for all these async calls to finish but I want to be able to define the subscribe callback
function for each of the calls since I need a reference to the process. Any ideas on how I can go about approaching this issue?
i tried with this function but always is return understand
getTasksForEachProcess(): Observable<Array<any>> {
let tasksObservables = this.organizationService.getOrganizationForUser().pipe(map((organization: Organization) => {
organization.contractOrganizations.map(contract => {
return this.conventionService.getById(contract.conventionId).subscribe(convention =>
this.conventions.push(convention)
)
});
})
);
return forkJoin(tasksObservables);
};
ngOnInit() {
this.getTasksForEachProcess().subscribe(item => {
console.log(item);
}
}
First of all I am not sure of what your are really trying to achieve, since I do not understand what you mean by
I want to be able to define the subscribe callback function for each
of the calls since I need a reference to the process
Anyways, in a situation like the one you describe, I would do something like this
public getOrganizationForUser(): Observable<Organization> {
return this.httpClient
.get<Organization>(`${c.serviceBaseUrl.sp}/organizationByUser`)
.pipe(catchError((err, source) => this.responseHandler.onCatch(err, source)));
}
public getById(id: number) {
return this.httpClient
.get<Convention>(`${c.serviceBaseUrl.sp}/conventions/` + id)
.pipe(catchError((err, source) => this.responseHandler.onCatch(err, source)));
}
ngOnInit() {
const obsOfObservables = this.OrganizationService.getOrganizationForUser()
.pipe(
map(organization => organization.contracts),
map(contracts => contracts.map(contract => this.conventionService.getById(contract.conventionId)))
);
obsOfObservables
.pipe(
switchMap(conventionObservables => forkJoin(conventionObservables))
)
.subscribe(
conventions => { // do stuff with conventions }
)
}
The key points here are the following.
Via getOrganizationForUser() you get an Observable which emits the Organization. The first thing you do you transform the object emitted by the Observable into an Array of contracts with the first map operator.
The second map operator transforms the Array of contracts into an Array of Observables of conventions. To perform this transformation we use the map method of Array within the map operator of Observable. This may be a bit confusing, but it is worth understanding.
If we stop here, what we have is obsOfObservables, i.e. an Observable which emits an Array of Observables.
We then pass the Array of Observables emitted by obsOfObservables to the forkJoin function, which in itself returns an Observable. Since we actually interested in what is notified by the Observable returned by forkJoin, i.e. we are interested in the conventions, then we need to switch from the first Observable to the second one, and this is done via switchMap operator.
The net result is an Observable which returns an Array of conventions. Consider that the constant obsOfObservables has been added as an attempt to clarify the reasoning and it is totally unnecessary (as Barney Panofsky would say).
I have not simulated the whole thing, so I hope I have not inserted mistakes, but more or less this is the thought process I would use in this case.
Last note, be generally suspicious when you have subscribe within subscibe.
I agree with Picci's logic in thinking through his answer. Here is a slight variation to what he proposed, though like him I have not rigorously tested this and there may be some errors.
The logic to this is that what you ultimately want is an array of convention, and producing an array from observables is what 'zip' does. So here is the flow:
get an organization, then create a stream of observables out of the organization.contracts array using rxjs' from.
each item in that stream will be a contract which will then be transformed (using map) into a convention based on and API lookup using the contract.conventionId property.
this whole resulting stream of observables of conventions will finally be transformed back into an array by the wrapping zip, and delivered as an observable that can be subscribed to resulting in the wanted array of conventions.
Here is the code:
ngOnInit() {
zip( this.OrganizationService.getOrganizationForUser()
.pipe(
map((organization: Organization) =>
from(organization.contracts).pipe(
map(contract => this.conventionService.getById(contract.conventionId))
)
)
)
)
.subscribe((conventions: Convention[]) => this.conventions = conventions)
}

RxJS Subscribe with two arguments

I have two observables which I want to combine and in subscribe use either both arguments or only one. I tried .ForkJoin, .merge, .concat but could not achieve the behaviour I'm looking for.
Example:
obs1: Observable<int>;
obs2: Observable<Boolean>;
save(): Observable<any> {
return obs1.concat(obs2);
}
Then when using this function:
service.save().subscribe((first, second) => {
console.log(first); // int e.g. 1000
console.log(second); // Boolean, e.g. true
});
or
service.save().subscribe((first) => {
console.log(first); // int e.g. 1000
});
Is there a possibility to get exactly that behaviour?
Hope someone can help!
EDIT:
In my specific use case obs1<int> and obs2<bool> are two different post requests: obs1<int> is the actual save function and obs2<bool> checks if an other service is running.
The value of obs1<int> is needed to reload the page once the request is completed and the value of obs2<bool> is needed to display a message if the service is running - independant of obs1<int>.
So if obs2<bool> emits before obs1<int>, that's not a problem, the message gets display before reload. But if obs1<int> emits before obs2<bool>, the page gets reloaded and the message may not be displayed anymore.
I'm telling this because with the given answers there are different behaviours whether the values get emitted before or after onComplete of the other observable and this can impact the use case.
There are several operators that accomplish this:
CombineLatest
This operator will combine the latest values emitted by both observables, as shown in the marble diagram:
obs1: Observable<int>;
obs2: Observable<Boolean>;
save(): Observable<any> {
return combineLatest(obs1, obs2);
}
save().subscribe((val1, val2) => {
// logic
});
Zip
The Zip operator will wait for both observables to emit values before emitting one.
obs1: Observable<int>;
obs2: Observable<Boolean>;
save(): Observable<any> {
return zip(obs1, obs2);
}
save().subscribe((vals) => {
// Note Vals = [val1, val2]
// Logic
});
Or if you want to use destructuring with the array
save().subscribe(([val1, val2]) => {
// Logic
});
WithLatestFrom
The WithLatestFrom emits the combination of the last values emitted by the observables, note this operator skips any values that do not have a corresponding value from the other observable.
save: obs1.pipe(withLatestFrom(secondSource))
save().subscribe(([val1, val2]) => {
// Logic
});
You can use forkJoin for this purpose. Call them parallely and then if either of them is present then do something.
let numberSource = Rx.Observable.of(100);
let booleanSource = Rx.Observable.of(true);
Rx.Observable.forkJoin(
numberSource,
booleanSource
).subscribe( ([numberResp, booleanResp]) => {
if (numberResp) {
console.log(numberResp);
// do something
} else if (booleanResp) {
console.log(booleanResp);
// do something
}
});
You may use the zip static method instead of concat operator.
save(): Observable<any> {
return zip(obs1, obs2);
}
Then you should be able to do like the following:
service.save().subscribe((x) => {
console.log(x[0]); // int e.g. 1000
console.log(x[1]); // Boolean, e.g. true
});
The exact operator to use depends on the specific details of what you are trying to solve.
A valid option is to use combineLatest - Docs:
obs1$: Observable<int>;
obs2$: Observable<Boolean>;
combined$ = combineLatest(obs1$, obs2$);
combined$.subscribe(([obs1, obs2]) => {
console.log(obs1);
console.log(obs2);
})
Concat emits two events through the stream, one after the other has completed, this is not what you're after.
Merge will emit both events in the same manner, but in the order that they actually end up completing, also not what you're after.
What you want is the value of both items in the same stream event. forkJoin and zip and combineLatest will do this, where you're getting tripped up is that they all emit an array of the values that you're not accessing properly in subscribe.
zip emits every time all items zipped together emit, in sequence, so if observable 1 emits 1,2,3, and observable two emits 4,5; the emissions from zip will be [1,4], [2,5].
combineLatest will emit everytime either emits so you'll get soemthing like [1,4],[2,4],[2,5],[3,5] (depending on the exact emission order).
finally forkJoin only emits one time, once every item inside it has actually completed,a and then completes itself. This is likely what you want more than anything since you seem to be "saving". if either of those example streams don't complete, forkJoin will never emit, but if they both complete after their final value, forkjoin will only give one emission: [2,5]. I prefer this as it is the "safest" operation in that it guarantees all streams are completing properly and not creating memory leaks. And usually when "saving", you only expect one emission, so it is more explicit as well. When ever you see forkJoin, you know you're dealing with a single emission stream.
I would do it like this, personally:
obs1: Observable<int>;
obs2: Observable<Boolean>;
save(): Observable<any> {
return forkJoin(obs1, obs2);
}
service.save().subscribe(([first, second]) => {
console.log(first); // int e.g. 1000
console.log(second); // Boolean, e.g. true
});
Typescript provides syntax like this to access the items in an array of a known length, but there is no way to truly create multiple arguments in a subscribe success function, as it's interface only accepts a single argument.

Why we should use RxJs of() function?

in service section of angular.io tutorial for angular2 I hit a method named of.for example :
getHeroes(): Observable<Hero[]> {
return of(HEROES);
}
or in below sample:
getHero(id: number): Observable<Hero> {
// Todo: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}
in angular.io Just explained
used RxJS of() to return an Observable of mock heroes
(Observable<Hero[]>).
and It was not explained why we should use of operator and exactly what does it do and what are its benefits?
The reason why they're using of() is because it's very easy to use it instead of a real HTTP call.
In a real application you would implement getHeroes() like this for example:
getHeroes(): Observable<Hero[]> {
return this.http.get(`/heroes`);
}
But since you just want to use a mocked response without creating any real backend you can use of() to return a fake response:
const HEROES = [{...}, {...}];
getHeroes(): Observable<Hero[]> {
return of(HEROES);
}
The rest of your application is going to work the same because of() is an Observable and you can later subscribe or chain operators to it just like you were using this.http.get(...).
The only thing that of() does is that it emits its parameters as single emissions immediately on subscription and then sends the complete notification.
Observable.of() is useful for maintaining the Observable data type before implementing an asynchronous interaction (for example, an http request to an API).
As Brandon Miller suggests, Observable.of() returns an Observable which immediately emits whatever values are supplied to of() as parameters, then completes.
This is better than returning static values, as it allows you to write subscribers that can handle the Observable type (which works both synchronously and asynchronously), even before implementing your async process.
//this function works synchronously AND asynchronously
getHeroes(): Observable<Hero[]> {
return Observable.of(HEROES)
//-OR-
return this.http.get('my.api.com/heroes')
.map(res => res.json());
}
//it can be subscribed to in both cases
getHeroes().subscribe(heroes => {
console.log(heroes); // '[hero1,hero2,hero3...]'
}
//DON'T DO THIS
getHeroesBad(): Array<Hero> {
return HEROES //Works synchronously
//-OR-
return this.http.get('my.api.com/heroes') //TypeError, requires refactor
}
In the first example, I assume that the HEROES variable is an array of objects, in which every object corresponds to interface Hero (or is the instance of class Hero). When we call this function somewhere in the code, it will act like this:
heroService.getHeroes()
.subscribe((heroes: Hero[]) => {
console.log(heroes)
// will output array of heroes: [{...}, {...}, ...]
})
It means, that when we use Observable's of creation method with array as argument, on subscription it will emit the whole array, not its' elements one-by-one
In the second example, when we subscribe to Observable, that was returned from getHero() method, it emits only one hero, whose id corresponds to given id.
Basically, when you create Observable with of method and supply arguments to it, on subscription it emits these arguments one-by-one
Here's a good reference

switchMap operation only running on first call?

I have an angular application that makes a request to an Http service and calls a switchMap on another Http service. For some reason the request in the switchMap only runs the first time the parent call is called. Otherwise the parent request fires and the switchMap one doesn't, here is the code:
this._receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result =>
// Refresh the lease receivables before giving result
this._receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
}).take(1).map(() => result)
)
.subscribe(
...
)
How can I make the getAll request in the switch map run every time the newTenantDebitCredit method is called above it?
Edit: Here is the entirety of the function that is called on click. when i click the button the first time for a given unit both methods are executed. If I try a Unit that has already had that method called (without a refresh) only the first method is executed. I realize a lot of this may not be clear it's a rather large project at this point.
public submitTenantCredit() {
this.isLoading = true;
let tenantCredit: NewTenantDebitCreditData;
let receivableDefinitions: ReceivableDefinition[] = [];
// construct receivable defintions for NewTenantDebitData model
receivableDefinitions = this._constructReceivableDefinitions();
// construct data we will be POSTing to server.
tenantCredit = new NewTenantDebitCreditData({
siteId: this._apiConfig.siteId,
leaseId: this.leaseId,
isCredit: true,
receivables: receivableDefinitions,
reason: this.actionReason
});
// make service call and handle response
this._receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result =>
// Refresh the lease receivables before giving result
this._receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
}).take(1).map(() => result)
)
.take(1)
.subscribe(
(receivables) => {
this.closeReasonModal();
let refreshLeaseId = this.leaseId;
this.leaseId = refreshLeaseId;
this.isLoading = false;
this.refreshBool = !this.refreshBool;
this._debitCreditService.refreshUnitInfo();
this._notifications.success(`The tenant credit for ${this.customerName} - Unit ${this.unitNumber} was submitted successfully`);
},
(error) => {
console.error(error);
this.isLoading = false;
}
)
}
If it helps newTenantDebitCredit() is a HTTP POST request and getAll() is a GET request.
You used take operator. When your service observable will emit then take operator will execute first and take will chain only first emit from observable. Subsequent emit will not taken by your code.
If you want to take all emits from observable then remove take from your code.
Hope it will help.
Testing the Rx code in isolation, here's a mockup. The console logs happen each time, so I think the Rx you're using is ok.
The best guess at a likely culprit is this.refreshBool = !this.refreshBool, but we'd need to see the internals of newTenantDebitCredit and getAll to be definitive.
// Some mocking
const _receivableService = {
newTenantDebitCredit: (tc) => {
console.log('inside newTenantDebitCredit')
return Rx.Observable.of({prop1:'someValue'})
},
getAll: (options) => {
console.log('inside getAll')
return Rx.Observable.of({prop2:'anotherValue'})
}
}
const tenantCredit = {}
// Test
_receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result => {
console.log('result', result)
return _receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
})
.take(1)
.map(() => result)
})
.take(1)
.subscribe(
(receivables) => {
console.log('receivables', receivables)
//this.refreshBool = !this.refreshBool;
},
(error) => {
console.error(error);
}
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
First of all, this has nothing to do with the switchMap operator.
Normaly removing the take(1) would cause this behaviour. In this case it wouldn't because it itsn't a so called hot observable.
The problem is that you are using a http.post. This is a cold observable which means it will only return a value once. That is also the reason why you don't need to unsubscribe. It will NEVER fire twice. Possible sollutions might be:
Using web sockets to get realtime data.
Creating a timer which will periodically fetch the data.
Simply get the data again whenever you need it.
The way you are asking the question
How can I make the getAll request in the switch map run every time the newTenantDebitCredit method is called above it?
actually sounds to me as if you are calling only newTenantDebitCredit from somewhere in your code, expecting the second request to happen; so I think this might be a misunderstanding of how observable chains work. Let's make an example:
const source$ = Observable.of(42);
source$
.map(value => 2 * value)
.subscribe(console.log);
source$
.subscribe(console.log);
What would you expect this to log? If your answer is "It would log 84 twice", then that is wrong: it logs 84 and 42.
Conceptually, your situation is the same. The second request only happens when the observable returned by newTenantDebitCredit() emits; it will not happen anytime some caller calls newTenantDebitCredit. This is because observable chains do not mutate an observable in-place, they only ever return a new observable.
If you want the second request to happen, you have to actually change the definition of the newTenantDebitCredit method to return an observable set up to perform the second request; alternatively, set up a chained observable that you subscribe to instead of calling newTenantDebitCredit.
Not really an answer but I did solve my problem. It will almost certainly be of no use to anyone BUT it was an issue in the receivableService it was not properly cheeking the boolean: refresh and was pulling values from cache after the first time.

Categories

Resources