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?
Related
If I fire the following function 10 times
createInterval() {
var someInterval = interval(10000).pipe( take(1)).subscribe(
_intervalValue =>
{
console.log(" Interval Fired" + new Date().toISOString());
});
}
I get 10 intervals that will console log 10 times. How do I know that I have 10 intervals?
Where can I access these, it's like they exist mysteriously somewhere.
How do I know that I have 10 intervals?
Let's simplify using numbers instead of Observables:
function createNumber() {
const someNumber = Math.random();
console.log(`Number Created: ${someNumber}`);
}
for(let x=0; x<10; x++) {
createNumber();
}
And re-ask the same question: "How do I know that I have 10 numbers?"
Well... you don't. Not unless you're saving a reference to them!
So let's have the function return a reference to the number:
function createNumber() {
const someNumber = Math.random();
console.log(`Number Created: ${someNumber}`);
return someNumber;
}
let myNumbers = [];
for(let x=0; x<10; x++) {
myNumbers.push(createNumber());
}
console.log(`I know I have ${myNumbers.length} numbers!`);
This behavior is no different for observables. If you want know you have 10 intervals, you need to keep track of them:
function createInterval() {
return interval(1000)
.pipe(take(1))
.subscribe(
() => console.log(`Interval Fired: ${ new Date().toISOString() }`)
);
}
let mySubscriptions: Subscription[] = [];
for(let x=0; x<10; x++) {
mySubscriptions.push(createInterval());
}
mySubscriptions.forEach(
sub => sub.unsubscribe()
);
Note: by calling .subscribe() you are returning a Subscription, not an Observable. It is often convenient to have functions return the observable, and let consumers of the function call .subscribe():
function createInterval() {
return interval(1000).pipe(
take(1),
tap(() => console.log(`Interval Fired: ${ new Date().toISOString() }`))
);
}
let myObservables: Observable<number>[] = [];
for(let x=0; x<10; x++) {
myObservables.push(createInterval());
}
const mySubscriptions = myObservables.map(
obs => obs.subscribe()
);
// then later on, you can unsubscribe
mySubscriptions.forEach(
sub => sub.unsubscribe()
);
I have a dropdown box that displays the list of States. There are around 40 States in the list.
Every time when I scroll down the list, the List displays only 15 to 20 States at a time.
I want to capture all the values of the list and save them in the string array. And then check alphabet sorting.
How can I do it using Cypress? Currently, It captures only the top 15 items from the list.
This is my code:
const verifySortOrdering = (key: string) =>
getSingleSelectList(key).then(dropdown => {
cy.wrap(dropdown).click();
if (dropdown.length > 0) {
const selector = 'nz-option-container nz-option-item';
let NumOfScroll = 1;
const unsortedItems: string[] = [];
const sortedItems: string[] = [];
cy.get(selector).then((listItem) => {
while (NumOfScroll < 7) {
sortAndCheck(selector, unsortedItems, sortedItems);
if (listItem.length < 15) {
break;
}
NumOfScroll++;
}
});
}
});
const sortAndCheck = (selector: string, unsortedItems: any, sortedItems: any) => {
cy.get(selector).each((listItem, index) => {
if (index === 15) {
cy.wrap(listItem).trigger('mousedown').scrollIntoView().last();
}
unsortedItems.push(listItem.text());
sortedItems = unsortedItems.sort();
expect(unsortedItems, 'Items are sorted').to.deep.equal(sortedItems);
});
};
Here's a working example based off of what you provided. Added an additional check to ensure the list has the right amount of options. You may or may not want that. Deep copying the unsorted list so it doesn't get sorted due to a shallow copy. Added validations after the unsortedItems list gets built so we can validate once instead of for every item in the list.
var unsortedItems = new Array()
var expectedListCount = 32
cy.get('#myselect>option').should('have.length', expectedListCount)
.each(($el) => {
unsortedItems.push($el.text());
}).then(() => {
var sortedItems = [...unsortedItems]; // deep copy
sortedItems.sort();
expect(unsortedItems).to.deep.equal(sortedItems)
})
Another example based on your revised sample but I can't verify it without having a working example of your DDL. This builds up the unsortedItem list first and then does the comparison.
const verifySortOrdering = (key: string) =>
getSingleSelectList(key).then(dropdown => {
cy.wrap(dropdown).click();
if (dropdown.length > 0) {
const selector = 'nz-option-container nz-option-item';
let NumOfScroll = 1;
const unsortedItems: string[] = [];
const sortedItems: string[] = [];
cy.get(selector).then((listItem) => {
while (NumOfScroll < 7) {
unsortedListBuilder (selector, unsortedItems, sortedItems);
if (listItem.length < 15) {
break;
}
NumOfScroll++;
}
});
var sortedItems = [...unsortedItems];
sortedItems.sort();
expect(unsortedItems).to.deep.equal(sortedItems);
}
});
const unsortedListBuilder = (selector: string, unsortedItems: any, sortedItems: any) => {
cy.get(selector).each((listItem, index) => {
if (index === 15) {
cy.wrap(listItem).trigger('mousedown').scrollIntoView().last();
}
unsortedItems.push(listItem.text());
});
};
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}`)));
}
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.
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);
});
});