Joining 2 nodes with angularfire2 - javascript

Image of my firebase
Im trying to map the categories that the user have favourited to the category node to retrieve its details.
Code for my typescript :
ionViewDidLoad(categorykey:string){
console.log('Hello Manage Favourite Page');
let userid = this.authData.getID();
this.favouritecategories = this.af.database.list(`/users/${userid}/favourites`)
.map(favourites => {
favourites.map(category => {
let categorykey = category.$key
console.log(categorykey);
category.favname = this.af.database.object(`/test/${categorykey}`)
});
return favourites;
});
Code for my Html:
<div *ngFor="let region of favouritecategories | async; let i = index;">
<ion-item (click)=" category.hidden = !category.hidden; category.visible = !category.visible" detail-none><b>{{i+1}}.{{ (category.favname| async)?.name }}</b>
<ion-icon name="arrow-down" [hidden]="category.visible" item-right></ion-icon>
<ion-icon name="arrow-up" [hidden]="!category.visible" item-right></ion-icon></ion-item>
</div>
With the above code , i only managed to retrieve the category's name but not the regions name. Anyone have any idea on how to do?

You haven't actually returned the modified map. You want to do something like the following (modified for latest AngularFire versions):
let listObservable = this.af.database.list(`/users/${userid}/favourites`);
// use snapshotChanges() as this contains the keys
this.favouritecategories = listObservable.snapshotChanges()
.map(snapshots => {
// note that we return the inner .map()!
return snapshots.map(snap => {
let data = snap.val();
data.favname = this.af.database.object(`/test/${snap.key}`).valueChanges();
// note also that we return the transformed data
return data;
});
});
Differences from the original poster's question:
Should use .valueChanges() to get a useful observable for ngFor ops
$key is no longer included in data by default
But can be obtained using .snapshotChanges()
Some other useful tips:
Avoid sequential numeric ids (i.e. arrays) in distributed data
Write the data how you'll read it later
Avoid trying to make NoSQL look like SQL with lots of fkey references and artificial normalizing. Just store the names where you'll read them as a general rule.
It's okay to denormalize data.

Related

I'm trying to make an array with only values ​that are not contained in the other array (non-repeating)

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()

Searching Array of Objects with two search values

so I am trying to make an app that has two search criterias. The front-end app basically fetches data and you have two search bars to filter out the incoming data.
One search is by name and the other is by school name, the tricky part is that the either of the search also takes into account if there is some value in the other search parameter.
For example, if you search for "California University" and "Bob", you should get only Bobs that go to California University to render on the screen. But it seems like right now my DOM only renders the most recent search Ive made. What is the best way to go about a filter that filters both student name and school name using an event listener (keyup) on the search inputs?
searchByNameInput.addEventListener("keyup", (e) => {
const filterNameArray = studentArray.filter((student) => {
// code here to filter students with this name and render it on DOM
}
}
searchBySchoolName.addEventListener("keyup", (e) => {
//filters students who go to this school and render it on DOM
}
}
Write a single filtering function that checks both inputs, and call it from both event listeners.
function filterStudents() {
const nameFilter = searchByNameInput.value;
const schoolFilter = searchBySchoolName.value;
const filterArray = studentArray.filter(student =>
(nameFilter == '' || student.name.includes(nameFilter) &&
(schoolFilter == '' || student.school.includes(schoolFilter))
}
searchByNameInput.addEventListener("keyup", filterStudents);
searchBySchoolNameInput.addEventListener("keyup", filterStudents);
first filter your object and please try it include() method instead of filter().
as a above example
here filterData is my new filtered object and stu_data is my array.
get all search value from search input.
Example:-
var filterData = stu_data.filter((stu_filter) => {
return (stu_filter.firstname.toLowerCase().includes(filter) ||
stu_filter.lastname.toLowerCase().includes(filter))})
I hope this is help for you!
happy coding :)

Fetching JSON objects based on key values

This code does a great job of fetching and rendering everything within the JSON array, but what if I am interested in only listing the objects with a particular key-value (like gender)? Would that happen during the fetch or the render?
const URL = "https://ghibliapi.herokuapp.com/people";
const main = document.getElementById("main");
main.innerHTML = "<p>Loading...";
fetch(URL).then((response) => response.json()).then((people) => main.innerHTML = getListOfNames(people));
const getListOfNames = (people) => {
const names = people.map((person) => `<li>${person.name} - ${person.gender} </li>`).join("\n");
return `<ul>${names}</ul>`;
};
The ideal case would be using GraphQL so you only fetch the data fields you need based on your criteria, in this case, there is no difference between changing the getListOfNames function for just outputting a person when its person.gender matches your criteria or simply passing to it a filtered array of people after fetching them all
You would have to configure the api endpoint to accept filters if you'd like to return filtered results. Otherwise, you'd filter on render.
With underscore you'd do _.where(response, {gender: 'male'})

How to "synchronize" Observables?

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).

Using Firebase Push key as Key in Second Push

I'm trying to add two related items to my Firebase database. I want to push one item, then get that item's newly created key and use it as the key for the second item in a different tree. I've tried querying the database to get the last key created and using it as the key for the second push, but it's still just generating a new key for it. Here's the code that I'm using:
save: function() {
if (this.$.document.isNew && (this.editableCard.title || this.editableCard.body)) {
return this.$.document.save(this.cardsPath).then(function() {
this.$.document.reset();
var sceneRef = firebase.database().ref().child(this.cardsPath);
var scene = sceneRef.orderByKey().limitToLast(1);
var sceneKey = scene.key;
this.$.document.save('/documents/', sceneKey);
}.bind(this));
}
return Promise.resolve();
}
(I'm using Polymer, and my starting point is the note-app demo for Polymerfire).
Any ideas on how I can retrieve the new key of the first push and use it for the second push? Thanks!
EDIT
I found the answer in Firebase's documentation for Reading and Writing to the database for Web. Link
push() returns a DatabaseReference immediately. You can ask that reference what its key is, using getKey(), then use that string to update another location in your database.
You can access the key property on the original database reference and use that as the key for the second one, like so:
let firstObjRef = firebase.database().ref('/first/path/).push(firstObj, (error) => {
videoObj["roomUploadedTo"] = this.roomName;
var updateObj = {};
updateObj[videoObjRef.key] = videoObj;
firebase.database().ref('/second/path/').update(updateObj).then( (e) => {
console.log('update went through. booyah! ' + e);
})

Categories