Multiple rxjs http requests Angular 6 - rxjs

I have following code:
private getUsers(page, result) {
result = result||[];
return this.http.get(API_URL + '/users?page=1')
.pipe(map(response => {
const response_filter = response.json();
const users = response_filter['data'];
const pages = response_filter['total_pages'];
Array.prototype.push.apply(result, users.map((user) => new User(user)));
while (page != pages)
{
this.http.get(API_URL + '/users?page=' + page)
.pipe(map(resp => {
console.log('test');
const response_filter = resp.json();
const users = response_filter['data'];
Array.prototype.push.apply(result, users.map((user) => new User(user)));
return result;
}))
.pipe(catchError(val => of(`Caught inner error: ${val}`)));
page += 1;
}
return result;
}))
.pipe(catchError(val => of(`Caught error: ${val}`)));
}
Code works good until console.log('test'). This log doesn't get shown, but while loop iterates fine.
Previously i tried the same function, but in recursive way. There was the same problem.

The best way to do this is to create a single observable which represents all of the requests you want to make, using flatMap and forkJoin operators. There are a number of problems with the asynchronous operations in your code, meaning that the returned result will not include the results of the inner HTTP requests.
I would propose the following:
private getUsers(page, result) {
return this.http.get(API_URL + '/users?page=1')
.pipe(
flatMap((response) => {
const response_filter = response.json();
const users = response_filter['data'];
const pages = response_filter['total_pages'];
let firstPageUsers: User[] = users.map((user) => new User(user));
let getAllUsers: Observable<User[]>[];
getAllUsers.push(of(firstPageUsers));
while (page < pages)
{
getAllUsers.push(this.http.get(API_URL + '/users?page=' + page)
.pipe(
map(resp => {
console.log('test');
const response_filter = resp.json();
const users = response_filter['data'];
return users.map((user) => new User(user));
}),
// You need to decide if this is how you want errors
// handled, it doesn't seem too sensible to me:
catchError((err) => {
console.log(`Caught inner error: ${err}`);
return of([]); // needs to return type Observable<User[]>
})
)
);
page += 1;
}
return forkJoin(getAllUsers);
}),
map((allResponses) => {
// allResponses will be an array of User arrays from
// all of the observables within the forkJoin, so now
// we can iterate over all of those to create a single
// array containing all of the results.
result = result||[];
allResponses.forEach((responseUsers) => {
Array.prototype.push.apply(result, responseUsers);
});
return result;
}),
catchError((err) => {
console.log(`Caught outer error: ${err}`);
of(null); // Or whatever - again, think about your error cases.
})
);
}
Now wherever you are calling getUsers, when you subscribe to this observable it should resolve all of the inner queries as well.

Marks answer is great, but I already solved my problem (maybe not in the good way, but solved it) using Martin comment (using subscribe). Firstly I subscribe for a "get pages count" request and then I'm subscribing to "get users" request in a while loop.
I'm new in angular, so maybe someone will answer a question "Must I use unsubscribe here?"
this._dataSub0 = this.userDataService.getPages().subscribe((pages) => {
var page_num = pages;
var i = 1;
while (i < page_num) {
this._dataSub = this.userDataService
.getAllUsers()
.subscribe(
(users) => {
for (let us of users) {
this.users.push(us);
}
}
);
i++;
}
});
public getAllUsers(page): Observable<User[]> {
return this.getUsers(page);
}
private getUsers(page) {
var result = result||[];
return this.http.get(API_URL + '/users?page=' + page)
.pipe(map(response => {
const response_filter = response.json();
const users = response_filter['data'];
const pages = response_filter['total_pages']
if(pages == page)
return null;
Array.prototype.push.apply(result, users.map((user) => new User(user)));
return result;
}))
.pipe(catchError(val => of(`Caught error: ${val}`)));
}
public getPages(): Observable<number> {
var result;
return this.http.get(API_URL + '/users?page=0')
.pipe(map(response => {
const response_filter = response.json();
const pages = response_filter['total_pages']
return pages;
}))
.pipe(catchError(val => of(`Caught error: ${val}`)));
}

Related

How to get results using a loop inside of switch map

I have the next code, and it was working properly. to execute a request to my method fetchDropdownDataByFederationId, but now I have a requirement to execute the same method x number of times.
fetchInProgress(queryString?): Observable<IPerson[]> {
let PersonList: IPerson[] = [];
return this.getItems<IPerson[]>('', queryString).pipe(
take(1),
switchMap((wls: IPerson[]) => {
PersonList = [...wls];
//const createdbyIds = [...new Set(wls.map((f) => f.createdBy))];
return this.teamPageService.getInformation(wls.createdBy);
}),
map((teams:any) => {
console.log('> teams', teams);
for (let i = 0; i < PersonList.length; i++) {
//update information
}
//console.log('> Final value: ', PersonList);
return PersonList;
})
);
}
But, I'm not finding a way to execute my SwitchMap x number of times and get the results back to use them in my map method to parse the information.
I just moved my SwitchMap to mergeMap, something like this:
mergeMap((wls: IWalklist[]) => {
//let allIds = wls.contact.map(id => this.getSingleData(id._id) );
let drops: Dropdown[] = [];
walklistList = [...wls];
const allIds = [...new Set(wls.map((f) => f.createdBy))];
return forkJoin(...allIds).pipe(
map((idDataArray) => {
drops.push(
this.teamPageService.getInformation('');
);
return drops;
})
)
}),
But still no luck.
Can some help me? how can I fix it?

RXJS listen to first subscription

I have a function that wraps observable with error handling, but to do so I need some code to run once it's inner observable is subscribed.
I also need that cancelling the higher Observable cancels the inner one, as it is doing HTTP call.
Context
slideshow: string[] = [];
currentIndex = 0;
private is = {
loading: new BehaviorSubject(false),
}
private loadImage(src: string): Observable;
private loadNextImage(index = this.currentIndex, preload = false): Observable<number> {
const nextIndex = (index + 1) % this.slideshow.length;
if (this.currentIndex == nextIndex) {
if (!preload) {
this.is.loading.next(false);
}
throw new Error('No other images are valid');
}
return ( possible code below )
}
Defer - This worked nicely until I realised this will create a new instance for every subscriber.
defer(() => {
if (!preload) {
this.is.loading.next(true);
}
return this.loadImage(this.slideshow[nextIndex]).pipe(
finalize(() => {
if (!preload) {
this.is.loading.next(false);
}
}),
map(() => nextIndex),
catchError(err => this.loadNextImage(nextIndex)),
);
});
Of(void 0).pipe(mergeMap(...)) - This does what is should, but it is really ugly
of(void 0).pipe(
mergeMap(() => {
if (!preload) {
this.is.loading.next(true);
}
return this.loadImage(this.slideshow[nextIndex]).pipe(
finalize(() => {
if (!preload) {
this.is.loading.next(false);
}
}),
map(() => nextIndex),
catchError(err => this.loadNextImage(nextIndex)),
);
}),
)
new Observable - I think there should be a solution that I am missing

Angularfire2 & Firestore – retrieve all subcollection content for a collection list

I try to retrieve datas in a subcollection based on the key received on the first call.
Basically, I want a list of all my user with the total of one subcollection for each of them.
I'm able to retrieve the data from the first Payload, but not from pointRef below
What is the correct way to achieve that?
getCurrentLeaderboard() {
return this.afs.collection('users').snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data()
const id = a.payload.doc.id;
const pointRef: Observable<any> = this.afs.collection('users').doc(`${id}`).collection('game').valueChanges()
const points = pointRef.map(arr => {
const sumPoint = arr.map(v => v.value)
return sumPoint.length ? sumPoint.reduce((total, val) => total + val) : ''
})
return { id, first_name: data.first_name, point:points };
})
})
}
I tried to put my code in a comment, but I think it's better formated as a answer.
First you need subscribe your pointRef and you can change your code like this.
getCurrentLeaderboard() {
return this.afs.collection('users').snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data()
const id = a.payload.doc.id;
const pointRef: Observable<any> = this.afs.object(`users/${id}/game`).valueChanges() // <--- Here
const pointsObserver = pointRef.subscribe(points => { //<--- And Here
return { id, first_name: data.first_name, point:points };
})
})
}
....
//Usage:
getCurrentLeaderboard.subscribe(points => this.points = points);
And if you going to use this function alot, you should start to denormalize your data.

Angular5 RXJS recursive http requests

I currently have this situation:
#Service My Service
private users = ['user1','user2'];
//Generate list of requests to join
private getHttpList(): any[] {
let gets = new Array();
for(let index in this.users)
gets.push(this.http.get('https://api.github.com/users/' + this.users[index]))
return gets;
}
...
getList(): Observable<any[]> {
return forkJoin(this.getHttpList())
}
And in my component, I do the subscribe
this.MyService.getList().subscribe(results => {
for(let res in results) {
//...Do something here
//..I wanna do the get in of https://api.github.com/users/{user}/starred
}
})
Suppose that I just know that the "starred url" after the result of getList(), how to I can "synchronous" this part, or what's the correct form to do this?
**I try do it hardcoded --Result id wrong, because the "res" is a "interable"
this.MyService.getList().subscribe(results => {
let url = 'https://api.github.com/users/';
for(let res in results) {//This don't do the things "synchronous"
this.http.get(url + res.login +'/starred').catch(err => {
throw new Error(err.message);
}).subscribe(starred_res => {
//So we set the starred_list
res.starred_list = starred_res
})
}
})
Thanks...
As I understand you want to get starred list for every user.
The simplest way is to get all starred lists and match them with users result.
// Get users
this.MyService.getList().subscribe((results: any[]) => {
const url = 'https://api.github.com/users/';
// Create requests to get starred list for every user
const starredRequests = results.map(
res => this.http.get('https://api.github.com/users/' + res.login + '/starred')
);
// Wait when all starred requests done and map them with results array
Observable.forkJoin(starredRequests).subscribe(starred => {
results.forEach((res, index) => {
res.starred_list = starred[index];
});
console.log(results);
});
});

Observable.bindCallback only returns value if I subscribe to it directly

Observable.bindCallback only returns value if I subscribe to it directly
in other words, this works fine:
return this.store.select(store => store.appDb.appBaseUrl)
.take(1)
.mergeMap(baseUrl => {
const url = `${baseUrl}?command=GetCustomers&resellerUserName=aaa&resellerPassword=bbbb`;
return this.http.get(url)
.map(res => {
var xmlData = res.text()
const boundCallback = Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
return boundCallback(this, xmlData)
.subscribe((x) => {
return x;
});
})
})
however I need to avoid the subscription as I am running inside #effect which auto subscribes for me, so I run:
return this.store.select(store => store.appDb.appBaseUrl)
.take(1)
.mergeMap(baseUrl => {
const url = `${baseUrl}?command=GetCustomers&resellerUserName=aaa&resellerPassword=aaa`;
return this.http.get(url)
.map(res => {
var xmlData = res.text()
const boundCallback = Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
var d:any = boundCallback(this, xmlData)
return d;
}).map(d=>{console.log(d)})
})
but instead of getting a value now I am getting a:
and this is d:
regards
Sean
If I understand what you want to do it should look something like this (obviously I didn't test it):
return this.store.select(store => store.appDb.appBaseUrl)
.take(1)
.mergeMap(baseUrl => {
const url = `${baseUrl}?command=GetCustomers&resellerUserName=aaa&resellerPassword=aaa`;
return this.http.get(url)
.mergeMap(res => {
var xmlData = res.text()
const boundCallback = Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
return boundCallback(this, xmlData)
}).do(d => console.log(d))
})
I used mergeMap() because I want to get the value from the Observable returned by boundCallback().
Also when using map() you always need to return a value that is propagated further. In your example you're not returning anything so you can use just do() to see print what values go through.
Edit:
So this is a simplified version of what you're trying to do.
class A {
private processXml(context, xmlData, cb) {
context.parseString(xmlData, {attrkey: 'attr'}, function (err, result) {
if (err || !result) return cb(null); return cb(result);
})
}
private parseString(str, _, cb) {
return cb(null, str);
}
private mockHttpGet() {
return Rx.Observable.of({
text: () => {
return 'abc';
}
});
}
test() {
return this.mockHttpGet()
.mergeMap(res => {
var xmlData = res.text();
const boundCallback = Rx.Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
return boundCallback(this, xmlData)
}).do(d => console.log(d))
}
}
let a = new A();
a.test().subscribe(val => console.log('subscribed result', val));
See live demo: https://jsbin.com/reweraw/2/edit?js,console
This demo prints:
abc
subscribe abc
The BoundCallbackObservable (and this applies to operators as well) do nothing until you subscribe to them. That's why in the debugger you see just raw data.
My demo works as you probably want so check out how am I using mergeMap() to get the actual value from the nested Observable and try to replicate the same logic in you application.
found the solution, I was one Obs too deep:
return this.store.select(store => store.appDb.appBaseUrl)
.take(1)
.mergeMap(baseUrl => {
const url = `${baseUrl}?command=GetCustomers&resellerUserName=reseller#ms.com&resellerPassword=XXXX`;
return this.http.get(url)
.map(res => {
return res.text()
}).flatMap(jData=>{
const boundCallback = Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
return boundCallback(this, jData)
}).map(businessData=>{
return businessData;
})
})

Resources