I have this piece of code:
ngOnInit(): void
{
this.categories = this.categoryService.getCategories();
var example = this.categories.flatMap((categor) => categor.map((categories) => {
var links = this.categoryService.countCategoryLinks(categories.id)
.subscribe(valeur => console.log(valeur));
return categories.id
}));
}
The result are two observables.
One consists in a list of categories.
The second one is the number of items for a particular categories.id.
My question is as follow:
How could I get all this information structured in a particular data structure?
I would like to store categories and the number of items per category in the same data structure to be able to show them up in my TS component.
I went step by step trying to fix my issues and I went to have the following code that is almost the solution:
this.categories = this.categoryService.getCategories();
var example = this.categories.mergeMap((categor) => categor.map((myCateg) =>
{
this.categoryService.countCategoryLinks(myCateg.id)
.map(numlinks => Object.assign(myCateg,{numLinks: numlinks}))
.subscribe(valeur => console.log(valeur));
return myCateg.id
}));
It gives the following output:
Where numLinks is still an object... (containing my count value) Any idea on how to transform it to a json property like categoryName or id??
Thanks in advance and Regards,
Here is the solution to the problem:
ngOnInit(): void
{
this.categories = this.categoryService.getCategories();
const example = this.categories
.mergeMap((categor) => categor
.map((myCateg) => {
this.categoryService.countCategoryLinks(myCateg.id)
.map(numlinks => {
myCateg.numlinks = numlinks.count;
return myCateg;
})
//Object.assign(myCateg,{numLinks: numlinks}))
.subscribe(value => console.log(value));
return myCateg
}));
example.subscribe(val => console.log("value2: "+val));
}
Once more, the solution comes from the mergeMap() operator. :-)
this.categoryService.getCategories()
.mergeMap(val => val) // "Flatten" the categories array
.mergeMap(category =>
this.categoryService.countCategoryLinks(category.id)
// Write the number of links on the `category` object
.map(numLinks => Object.assign(category, {numLinks: numLinks}))
)
.toArray()
.subscribe(allCategoriesWithNumLinks => console.log(allCategoriesWithNumLinks));
I'm not going into the specifics (the first mergeMap to flatten the array, Object.assign() to produce the final object) since it seems like we covered all that in a previous thread we had, but feel free to ask questions if anything is unclear.
Philippe's questions:
Why flatten the categories array? I'm assuming getCategories() emits a SINGLE array containing all the categories. Since you want to run an HTTP request for each category, it's more convenient to have an observable emitting each category individually. That's what the first mergeMap() does: it transforms Observable<Category[]> into Observable<Category>.
Why create an object? You said you wanted to store everything in the same data structure. That's what Object.assign does: it writes the number of links found for each category on the category object itself. This way, you end up with ONE object containing the information from TWO observables (category + numLinks).
Related
I have an array of available users that can be invited and also another array with all joined users to the particular chat. I need to check which of the available users have joined the chat and should be listed elsewhere.
Finally, I want to get an array with only the available users who have not joined the chat.
let availablеUsers = [{id:1,name:'Dani'}, {id:2,name:'Ani'}, {id:3,name:'Marta'}]
let allUsers = [{id:2,name:'Ani'},{id:10,name:'John'}, {id:3,name:'Marta'}]
The first thing I try to do is find those who are already participating in the chat:
let joinedUsers = availablеUsers.map((user) => {
return allUsers?.find((u) => u.id === user.id);
});
And i get this : [undefined, {… Аni}, {… Marta}]
Then I try to filter the array of available users so that I remove from it those that are in the newly created array and here's the problem I don't know how to do this :/
My idea is something like that:
availablеUsers = availablеUsers.filter((user) => {
//HERE I don't know what logic to write
return joinedUsers?.map((m) => m?.id !== user.id); // this doesn't work, just an example
});
My goal is to have only those users not contained in the other remain in the availableUsers array.
In the example I have given at the end in the array should remain only {id:1,name:'Dani'}
I welcome any suggestions. If it can do it with chaining, without the extra variable for joinedUsers it would be even better!
There's no need for joinedUsers. Just use find() or some() in the filter() callback, and invert the test.
availableUsers = availableUsers.filter(user => !allUsers.some(u => u.id == user.id))
if users are uniquely identified by id you can use just a filter with a Set of known users:
let availablеUsers = [{id:1,name:'Dani'}, {id:2,name:'Ani'}, {id:3,name:'Marta'}]
let allUsers = [{id:2,name:'Ani'},{id:10,name:'John'}, {id:3,name:'Marta'}]
let joinedUsers = availablеUsers.filter(
function ({id}) {
return this.has(id);
},
new Set(allUsers.map(({id}) => id))
);
Accordingly, you can use the same to update availablеUsers in one go:
availablеUsers = availablеUsers.filter(
function ({id}) {
return !this.has(id);
},
new Set(allUsers.map(({id}) => id))
);
it's not super clear why or when you need !== vs === but the concept is: use a set and use filter instead of map when you want to filter + a Set works harder while constructed but it's blazing fast while used via has()
I have a state set as
const [filteredProducts, setFilteredProducts] = useState([]);
I want to be able to append to the end of that state. I am currently trying
products.forEach((product) => {
if (product.category === category) {
setFilteredProducts([...filteredProducts, product]);
}
});
It it looping through the products array correctly. I can even log the product after the setFilteredProducts and it logs the correct ones I want. I am calling this with an onClick.
Find all the products you want to add:
const productsToAdd = products.filter(product => product.category === category)
Then append them
setFilteredProducts((currentFilteredProducts) => ([...currentFilteredProducts, ...productsToAdd]));
The issue with your example is that filteredProducts may get stale after the first iteration. setFilteredProducts will not run synchronously, and filteredProducts keep the original value, until the re-render happen.
You would only append the last match to the existing filteredProducts array.
You can add all matches like so:
setFilteredProducts([...filteredProducts, ...products.filter((product) => product.category === category)]);
I'd recommend you do this in 2 steps:
Create an array of the new products you plan to add
let productsToAdd = [];
products.forEach((product) => {
if (product.category === category) {
productsToAdd.push(product);
}
});
Then combine the arrays and set state
setFilteredProducts([...filteredProducts, ...productsToAdd]);
I think you want what the ES6 built-in function does. You can rewrite your code to give you the the products that match the category like this:
const filteringTheProducts = products.filter(product => {
return product.category === category
})
setFilteredProducts(filteringTheProducts)
The result of the filtering will be the array of all the products that match that criteria.
Here is the documentation for .filter()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
The problem is, that setFilteredProducts doesn't immediately affect products. It's React's job to decide when to update the state. So when you loop over products, you'll probably ending up adding just the last item, because filteredProducts wasn't updated yet.
What you can do, is preparing an array of products to add:
const productsToAdd = products.filter(product => product.category === category);
And then append them:
setFilteredProducts([...products, ...productsToAdd]);
I want to retrieve a list of products in relation to the user's position, for this I use Geofirestore and update my Flatlist
When I have my first 10 closest collections, I loop to have each of the sub-collections.
I manage to update my state well, but every time my collection is modified somewhere else, instead of updating my list, it duplicates me the object that has been modified and adds it (updated) at the end of my list and keep the old object in that list too.
For example:
const listListeningEvents = {
A: {Albert, Ducon}
B: {Mickael}
}
Another user modified 'A' and delete 'Ducon', I will get:
const listListeningEvents = {
A: {Albert, Ducon},
B: {Mickael},
A: {Albert}
}
And not:
const listListeningEvents = {
A: {Albert},
B: {Mickael},
}
That's my useEffect:
useEffect(() => {
let geoSubscriber;
let productsSubscriber;
// 1. getting user's location
getUserLocation()
// 2. then calling geoSubscriber to get the 10 nearest collections
.then((location) => geoSubscriber(location.coords))
.catch((e) => {
throw new Error(e.message);
});
//Here
geoSubscriber = async (coords) => {
let nearbyGeocollections = await geocollection
.limit(10)
.near({
center: new firestore.GeoPoint(coords.latitude, coords.longitude),
radius: 50,
})
.get();
// Empty array for loop
let nearbyUsers = [];
// 3. Getting Subcollections by looping onto the 10 collections queried by Geofirestore
productsSubscriber = await nearbyGeocollections.forEach((geo) => {
if (geo.id !== user.uid) {
firestore()
.collection("PRODUCTS")
.doc(geo.id)
.collection("USER_PRODUCTS")
.orderBy("createdDate", "desc")
.onSnapshot((product) => {
// 4. Pushing each result (and I guess the issue is here!)
nearbyUsers.push({
id: product.docs[0].id.toString(),
products: product.docs,
});
});
}
});
setLoading(false);
// 4. Setting my state which will be used within my Flatlist
setListOfProducts(nearbyUsers);
};
return () => {
if (geoSubscriber && productsSubscriber) {
geoSubscriber.remove();
productsSubscriber.remove();
}
};
}, []);
I've been struggling since ages to make this works properly and I'm going crazy.
So I'm dreaming about 2 things :
Be able to update my state without duplicating modified objects.
(Bonus) Find a way to get the 10 next nearest points when I scroll down onto my Flatlist.
In my opinion the problem is with type of nearbyUsers. It is initialized as Array =[] and when you push other object to it just add new item to at the end (array reference).
In this situation Array is not very convenient as to achieve the goal there is a need to check every existing item in the Array and find if you find one with proper id update it.
I think in this situation most convenient will be Map (Map reference). The Map indexes by the key so it is possible to just get particular value without searching it.
I will try to adjust it to presented code (not all lines, just changes):
Change type of object used to map where key is id and value is products:
let nearbyUsersMap = new Map();
Use set method instead of push to update products with particular key:
nearbyUsersMap.set(product.docs[0].id.toString(), product.docs);
Finally covert Map to Array to achieve the same object to use in further code (taken from here):
let nearbyUsers = Array.from(nearbyUsersMap, ([id, products]) => ({ id, products }));
setListOfProducts(nearbyUsers);
This should work, but I do not have any playground to test it. If you get any errors just try to resolve them. I am not very familiar with the geofirestore so I cannot help you more. For sure there are tones of other ways to achieve the goal, however this should work in the presented code and there are just few changes.
I have an observable array and would like to get the sum of a property values in that array. My array is defined as:
public bookStores$: Observable;
I was going to do a simple for loop and calculate the sum, but I get a syntax error when trying to use the count property of my array:
Operator '<' cannot be applied to types 'number' and '<T>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>)=>boolean)...
This occurs when I do:
for (let i = 0; i < this.bookStores$.count; i++){ }
Every item in my array of BookStore objects has a property called numberOfBooks. What is the proper way to get the sum of those values contained on each BookStore object in my BookStore array?
This is why you're getting unexpected results for Observable.count
To get the array lenght of the results, you need to do, something like this:
BookStoreService.ts
...
getBookStore() : Observable<Bookstore> {
this.bookstore$ = this.http.get(...)
.map( (response:Response) => response.json() )
.map( response => response.bookstore); // optional depends if JSON payload you want is wrapped inside some other container
return this.bookstore$;
}
Component.ts
...
bookstore$ : Observable<Bookstore>;
bookstores: Bookstore[];
numberOfBookStores:number;
constructor(private bookService:BookService) {}
..
this.bookService.getJobs()
.subscribe(
bookstores => {this.bookstores = bookstores;
this.numberOfBookstores = this.bookstores.length;
},
err => { this.bookstores = [] as BookStore[];
// Caters for 404 etc error - be sure to be more robust in final code as you may want to indicate types of error back to user.
},
() => {
}
);
Update:
If you only need to loop through the list in yourHTML template, then
then defining the bookstores array as a property would not be necessary. I did this to illustrate how to get the size of the returned collection of bookstores.
You can use this type of syntax:
<tr *ngFor="let bookstore of (bookstore$ |async) as bookstores;
trackBy bookstore?.id; let i = index">
<td>{{bookstore.numberofBooks}}
<tr/>
You can find out more about:
*ngFor trackBy, even, odd, first, last here.
Using Async pipe for entire block of html template with AS here
Furthermore have a look at libraries like Lodash and Underscore for summing count of number of books. I've not used Underscore myself.
Here's a simple example to get you started.
If you want to get more adventurous have a look at this Functional Programming in Javascript Tutorial
I suspect this isn't hard, but I can't figure out how to do it. I have an array of objects, and each object contains an array. After doing some additional processing, I want to retrieve a remote resource for each of the elements. My problem is that my function returns an array, but I want the elements of the array separate.
return Rx.Observable.from([ // 1
{ "rosters": ["a/name1", "b/name2"] },
{ "rosters": ["c/name3", "c/name4"] }])
.map(group => group.rosters) // 2
.map(roster => roster.substring(0, roster.indexOf('/'))) // 3
.distinct() // have I seen this before?
.map(folder => http.get(URL + folder + '/ads.json').map(res => res.json())
.map(adData => adData.ads)
.reduce(/* choose random ad */);
I want the function at #2 to return data such that each enter of #3 is a separate string. Right now coming into #3 are arrays (in my example pairs of strings). Thanks!
With assistance both from the OP as well as from #tmslnz...
Change .map(group => group.roster) to .flatMap(group => group.roster).