Reputation: 699
I have a component getting data from a Service:
ngOnInit(): void
{
this.categoryService.getCategories().subscribe((data) => {
this.categories=data;
}
)
}
and here is the Service code:
getCategories(){
return this.category.find({where: {clientId: this.userApi.getCurrentId()}}).map((data) => {
return data;
}, err => {
console.log(err);
});
};
the returned data structure is:
{"categoryName":"Google","id":"58591d2b7672d99910497bec","clientId":"585808f6737f6aad1985eab2"},{"categoryName":"Yahoo","id":"58591d4c7672d99910497bef","clientId":"585808f6737f6aad1985eab2"},{"categoryName":"Msn","id":"585d25f6ae4b2ecb056bc514","clientId":"585808f6737f6aad1985eab2"}
I have another method on this.categories
called countLinks(id)
; id
is a category parameter like 585d25f6ae4b2ecb056bc514
, taken from the data structure.
countLinks()
returns an observable containing all of the links for that particular category.
I would like to add to my data structure another property containing the number of links, per category id, of course. Something like :
{"categoryName":"Google","id":"58591d2b7672d99910497bec",**"nblinks":"0"**,"clientId":"585808f6737f6aad1985eab2"},
{"categoryName":"Yahoo","id":"58591d4c7672d99910497bef",**"nblinks":"5"**,"clientId":"585808f6737f6aad1985eab2"},
{"categoryName":"Msn","id":"585d25f6ae4b2ecb056bc514",**"nblinks":"12"**,"clientId":"585808f6737f6aad1985eab2"}
To add information, categories is Array Type :
Any idea?
Upvotes: 0
Views: 1477
Reputation: 12666
First of all, what you are doing is horrible practice. Here is how I break down your problem:
If you are hitting the same backend for both calls, you need to write an endpoint to get all the data you need, period. If you are hitting two backends, you need to pass an array of clientIds and get number of links in one call. If you don't control the second call backend, then fine, let's do it like outline in steps 1 and 2.
Your structure will need to look like this:
.forkJoin
Observable to make all your individual calls - this works like Q.all, if you are familiar with that. I am too lazy to write the complete code for you, sorry, but basically, when you get your data, you need to create an Observable for EACH value returned, like this:
//This function will generate observables for you, when passed a param
function generateMockLinkCallObs(clientId){
var mockLinksCall = new Rx.Observable(observer => {
var mockCallTime = Math.floor(Math.random() * 5000) + 1000;
setTimeout(() => {
var mockLinks = Math.floor(Math.random() * 12) + 1;
observer.next(mockLinks);
}, mockCallTime)
})
return mockLinksCall;
}
//This just uses the function above to generate your observables for EACH object
generateListOfObservableCalls(ids){
var obsArray = [];
var obs;
ids.forEach(id => {
obs = generateMockLinkCallObs(id);
obsArray.push(obs);
})
return obsArray;
}
//This just uses the above function.
getNumberOfLinks(data){
var ids = data.map(data => data.id)
var observables = generateListOfObservableCalls(ids);
return observables;
}
So, to put it all together, you get your initial data:
httpWhatever.subscribe(
next => {
var observablesForIndividualCalls = getNumberOfLinks(next);
Observable.forkJoin(observablesForIndividualCalls)
.subscribe((response) => {
console.log(response[0], response[1]);
});
}
)
I over-simplified the last step, but forkJoin will only complete once you get results from all your individual calls.
And no, I don't know of a simpler way to do it, though it can definitely be cleaner.
Upvotes: 2
Reputation: 14087
What you describe is a typical use case for the mergeMap() operator.
Try this code:
getCategories() {
return this.category.find({where: {clientId: ID}})
.mergeMap(category =>
countLinks(category.id).map(categoryLinks => {
return Object.assign({}, category, { nblinks: categoryLinks.length })
})
);
};
I am mapping the first observable (returned by this.category.find()
) to another observable (returned by countLinks()
).
In the innermost map()
, I end up with both the category
and the categoryLinks
and I merge them into a single object with Object.assign()
.
Side note: Your original code is slightly confusing. You wrote "I have another method on this.categories called countLinks(id)" but you also wrote this.categories=data
. How can you have a method on a data structure?.. I wrote my answer under the assumption that both this.category.find()
and countLinks()
return observables, but you'll have to adapt my solution to your specific code/situation.
Upvotes: 2
Reputation: 215
Create two interfaces. One to represent the base of the category and one that extends it.
interface ICategory {
categoryName: string;
id: string;
clientId: string;
}
interface ICategoryExpanded extends ICategory {
nbLinks: string;
}
Then use the .map() operator in RxJS to transform the data into the 2nd interface.
this.getCategories().map((cats: ICategory[]) => {
let mapCategories: ICategoryExpanded[] = [];
cats.forEach((cat: ICategory) => {
let transformCat: ICategoryExpanded = <ICategoryExpanded>cat;
transformCat.nbLinks = this.countLinks(transformCat.id);
mapCategories = [...mapCategories, transformCat];
});
return mapCategories;
})
.subscribe((returnCategoriesExt) => this.categories = returnCategoriesExt);
That should handle what you want and return something like this:
[{ "categoryName": "Google", "id": "58591d2b7672d99910497bec", "clientId": "585808f6737f6aad1985eab2", "nbLinks": "5" },
{ "categoryName": "Yahoo", "id": "58591d4c7672d99910497bef", "clientId": "585808f6737f6aad1985eab2", "nbLinks": "5" },
{ "categoryName": "Msn", "id": "585d25f6ae4b2ecb056bc514", "clientId": "585808f6737f6aad1985eab2", "nbLinks": "5" }]
Upvotes: 0