I have an array of objects. I need to get array if ids, then call 2 APIs, then close the modal window.
My code is:
from(this.alerts)
.pipe(map(alert => alert._id))
.subscribe(alertIds => zip(
this.alertApi.someCall1(alertIds, ...),
this.alertApi.someCall2(alertIds, ...),
).subscribe(() => {
this.activeModal.close();
}),
);
Do you have any idea with preventing subscribe inside subscribe?
Use switchMap rxjs operator to avoid nested subscriptions.
from(this.alerts)
.pipe(
map(alert => alert._id),
switchMap(alertIds => zip(
this.alertApi.someCall1(alertIds, ...),
this.alertApi.someCall2(alertIds, ...)
))
)
.subscribe(() => {
this.activeModal.close();
);
More information on switchMap operator can be found here.
You can use forkJoin, which is similar to Promise.all, and switchMap.
See also: the RxJS docs, specifically example 6, which is similar to your situation.
from(this.alerts)
.pipe(
switchMap(alert =>
forkJoin(
this.alertApi.someCall1(alert._id),
this.alertApi.someCall2(alert._id)
)
)
)
).subscribe(() => {
this.activeModal.close();
});
Please note the resulting observable will only emit if all inner observables complete.
Related
So, i am new to RXJS, and i have checked a lot of stackoverflow and documentation before coming here and asking this, but i'm finding a hard time to make my logic work.
I have an Observable that will fetch a collection of documents and return them, and i use the pipe operator to make some changes, like using the map operator to change the object. So far, everything is fine.
The problem is here. Afterward, i need to run an "http request" for every document, in order to get specific data about them ("tags"). The http request is of course made as an Observable too, that needs to get subscribed on to fetch the data. However, the subscription takes some time, and the resulting object afterward doesn't have the required data.
let myFunction.pipe(
// mapping to add missing data needed for the front-end
map((results) => ({
...results,
documents: results._embedded.documents.map((document) => ({
...document,
tags: []
})),
})),
// mapping to loop through each document, and use the observable to get the tags with the document id
map((results) => {
let documents = results.documents.map((document: Document) => {
// get Tags for each document
let tagsToReturn = []
this.getDocumentTags(document.id)
.pipe(
// map function to return only the ids for each document, and not the complete tag object
map((tagsArray) => {
const modifiedTagsArray = tagsArray.map((tagObject: any) => {
if (tagObject !== undefined) {
return tagObject.id
}
})
return modifiedTagsArray
})
)
// the subscription to "actually" get the tags
.subscribe((tagsArray: number[]) => {
// Here the tags are found, but the latter code is executed first
// document.tags = tagsArray
tagsToReturn = tagsArray
})
// console.log(JSON.stringify(document))
// Here the tags are not found yet
console.log(JSON.stringify(tagsToReturn))
return { ...document, tags: tagsToReturn }
})
// I then, normally return the new documents with the tags for each document, but it is empty because the subscribe didn't return yet.
return {
_links: results._links,
page: results.page,
documents: documents,
}
}),
map((results) => {
results.documents.forEach((doc) => {
return this.addObservablesToDocument(doc)
})
return results
})
)
I have tried some solutions with switchmap, forkjoin, concat...etc but it didn't work, or i didn't find the correct way to use them. This is why i'm asking if there is a way to stop or another way to handle this problem.
I have tried using different operators like: mergemap, concat, switchmap to swich to the new request, but afterward, i can't have the global object.
I mostly tried to replicate/readapt this in some ways
By using mergemap combined with forkjoin, i was able to replicate what you were looking for.
Not really sure of how i can explain this, because i'm also not an expert coming to Rxjs, but i used the code from : this stackoverflow answer that i adapted
How i understand it is that, when using mergeMap in the pipe flow, you make sur that everything that get returned there, will be executed by the calling "subscribe()",then the mergeMap returns a forkJoin which is an observable for each document tags
I hope this can help
.pipe(
// mapping to add missing data needed for the front-end
map((results) => ({
...results,
documents: results._embedded.documents.map((document) => ({
...document,
tags: []
})),
})),
/******** Added Code *********/
mergeMap((result: ResultsNew<Document>) => {
let allTags = result._embedded.documents.map((document) =>
this.getDocumentTags(document.id).pipe(
map((tagsArray) => tagsArray.map((tagObject: any) => tagObject.id))
)
)
return forkJoin(...allTags).pipe(
map((idDataArray) => {
result._embedded.documents.forEach((eachDocument, index) => {
eachDocument.tags = idDataArray[index]
})
return {
page: result.page,
_links: result._links,
documents: result._embedded.documents,
}
})
)
}),
/******** Added Code *********/
map((results) => {
results.documents.forEach((doc) => {
return this.addObservablesToDocument(doc)
})
return results
})
)
I would like to subscribe two observables one after the other. The order is important and must be kept. The first observable returns a result itemId which must be passed to the second subscription. Currently, I use nested subscriptions, which is not very nice. What is the cleanest way to implement this?
// 1
this.widget$
.subscribe((widget) => {
const itemId: number = widget.data[0].itemId;
// 2
this.store
.select(DeviceHistoryStore.getItemHistoryEntries(this.deviceId, itemId))
.subscribe((deviceHistory) => {
const name = widget.name;
// Run code
});
});
Simply use a SwitchMap.
this.widget$.pipe(
switchMap(widget =>
this.store
.select(DeviceHistoryStore.getItemHistoryEntries(
this.deviceId,
widget.data[0].itemId
))
)
).subscribe(deviceHistory => { /* ... */ )
Edit:
If you want to access widget in the subscribe callback:
this.widget$.pipe(
switchMap(widget =>
combineLatest([
of(widget),
this.store
.select(DeviceHistoryStore.getItemHistoryEntries(
this.deviceId,
widget.data[0].itemId
))
])
)
).subscribe(([widget, deviceHistory]) => { /* ... */ )
I am new to rxjs and am trying to do two requests. When I try to see the result, I get Observable.
copy() {
const obj = {};
this.create(skill)
.pipe(
mergeMap((res) => {
return [res, forkJoin(this.levels.map((level) => this.level(level)))];
}),
)
.subscribe((res) => {
console.log(res);
});
}
level(level) {
return this.create(level);
}
Output:
object of created skill,
Observable {_isScalar: false, _subscribe: ƒ}
I get the response of the first request normally and the second one comes to me as "Observable".
I'm not completely sure I understand what you're trying to do :-)
The function you pass to mergeMap() should "usually" return an observable. Currently, you are returning an array.
When you return array, mergeMap will simply emit each array element; which is why you receive those two emissions created skill, Observable.
However, if you return Observable, mergeMap will subscribe to it and emit.
I think this could work for you:
copy() {
this.create(skill).pipe(
mergeMap(createdSkill => forkJoin(this.levels.map(l => this.level(l))).pipe(
map(createdLevels => ([createdSkill, createdLevels]))
)
)
.subscribe(
([skill, levels]) => console.log({skill, levels})
);
}
It might be easier to follow if we break it down into smaller chunks:
createLevels() {
return forkJoin(this.levels.map(l => this.level(l));
}
copy() {
this.create(skill).pipe(
mergeMap(createdSkill => createLevels()).pipe(
map(createdLevels => ([createdSkill, createdLevels]))
)
)
.subscribe(
([skill, levels]) => console.log({skill, levels})
);
}
Looking it this way seems like we could instead build the copy() method in a simpler way:
copy(skill) {
forkJoin(
this.createSkill(skill),
this.createLevels()
)
.subscribe(
([skill, levels]) => console.log({skill, levels})
);
}
ForkJoin might not be the optimal operator here. I suggest having a look at the operator decision tree. I believe you want something like this though?
copy() {
const obj = {};
forkJoin(
[
this.create(skill),
...this.levels.map((level) => this.level(level))
]
).subscribe((res) => {
console.log(res);
});
}
level(level) {
return this.create(level);
}
I have an API (getNewStories) that returns the data as an array of numbers(ids) such as [1,2,3,4...].
There is another API (getItem) that uses the number(id) and give its details.
How can I accomplish this with rxjs operators, so that I should only subscribe to it once and it gives me an array of the records with those ids?
I am able to accomplish this using 2 subscriptions, but I want it with one. Is it possible? and if it's, then, how?
this.hnService.getNewStories().subscribe(data => {
// data is [1,2,3,4,5]
// create an array of observables for all the ids and get the record for that id
const observables = data.map(item => this.hnService.getItem(item));
// use forkJoin to combine the array to single results variable
forkJoin(...observables).subscribe(results => {
this.stories = results;
});
});
with this I have to subscribe to both the APIs.
You were going the right direction with using forkJoin:
this.hnService.getNewStories()
.pipe(
concatMap(data => {
const items$ = data.map(item => this.hnService.getItem(item));
return forkJoin(...items$);
}),
)
.subscribe(allItems => ...);
forkJoin will wait until all source Observables complete and only then emit all results as a single array.
I think you can achieve this using a flattening operator like this.
this.hnService.getNewStories().pipe(
.mergeMap(data => this.hnService.getItem(data))
.subscribe(res => this.stories = res);
Other option can be to create two observable streams and use a combineLatest.
You can simply have implemented as in the the following snippet: (Yes, mergeAll flattens an observable containing an array, for further explanation refer to #Martin's post about the Best way to “flatten” an array inside an RxJS Observable
)
getNewStories().pipe(mergeAll(), concatMap(this.getItem), toArray()).subscribe()
You can try running the following snippet:
const { of } = rxjs;
const { concatMap, toArray, mergeAll } = rxjs.operators;
function getItem(x) {
return of({ item : x })
}
of([1, 2, 3, 4])
.pipe(
mergeAll(),
concatMap(getItem),
toArray()
)
.subscribe(console.log)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.6.2/rxjs.umd.min.js"></script>
Imagine I have 3 functions, taking some arguments and returning an observable. Logic would be (all would be a result of something async):
Get a cat.
Pet the cat.
Get food for the cat based on it's mood after petting it.
Sample code:
function getCat(): Observable<Cat> {
return Observable.of({ name: 'Larry' })
}
function petCat(cat: Cat): Observable<Mood> {
return Observable.of(Mood.HAPPY)
}
function getFood(cat: Cat, mood: Mood): Observable<Food> {
return Observable.of({ type: 'fish' })
}
I would like to end up with an Observable that's equivalent with this:
Observable.from([{ name: 'Larry' }, Mood.HAPPY, { type: 'fish' }])
I would like the functions to be invoked in sequence (obviously from the parameter signatures)
I would not like to delay the individual emits, in the final observable I'd like to get the results as soon as they arrive.
I could get it working with something like this:
function perform() {
return getCat().pipe(
mergeMap((cat) => concat(
of(cat),
petCat(cat).pipe(
mergeMap((mood) => concat(
of(mood),
getFood(cat, mood)
))
)
)
),
)
}
Question is, is there a better, more readable way of doing this (let's say I would have 5 of these that I would like to chain)?
You can try something like
getCat()
.pipe(
switchMap(cat => petCat(cat).pipe(map(mood => ({cat, mood})))),
switchMap(({cat, mood}) => getFood(cat, mood))
)
The whole idea is to use switchMap to switch from the source Observable to the Observable returned by the function passed to switchMap as parameter.
Maybe worth of mentioning is the use of map chained into the pipe of the first switchMap. That map operator makes sure that we keep cat as part of the parameters we pass the second, and last, switchMap.
UPDATE after comment
If the function perform has to emit all the 3 elements emitted by the 3 functions, then you may try something like the following
function perform() {
return getCat()
.pipe(
switchMap(cat => petCat(cat).pipe(map(mood => ({cat, mood})))),
switchMap(({cat, mood}) => getFood(cat, mood).pipe(map(food => ({cat, mood, food}))))
)
}