Joins in the Firebase Database with Polymer Properties - javascript

GOOD SOLUTION HERE
In the case below I am trying to get data from one location and then find the related data in a different location (the firebase joins).
I am able to retrieve the appropriate data and display in the my console but I kind of stuck when it comes to storing them in one of my properties to then loop over them with a dom-repeat template. In addition I am not entirely sure if this should be done with plane JavaScript or the PolymerFire components.
//Key value for a course that I retrieved from a URL Query
var ckey = KSH456YU789;
//Get the Videos that belong to that course key
firebase.database().ref('/courseVideos/' + ckey).on('child_added', snap => {
//Get the video data of each video that belongs to the course
let videoRef = firebase.database().ref('videos/' + snap.key);
videoRef.once('value', function(snapShot) {
this.set('courseVidObj', snapShot.val());
//console.log() the data works
console.log(this.courseVidObj);
}.bind(this));
});
As it can be seen above I am able to log the data that is stored in my property 'courseVidData' which is from type Array. However, this is run for each request which basically overwrites the previous stored value.
This makes it impossible to use my property inside a dom-repeat template. As shown below:
<template is="dom-repeat" items="[[courseVidData]]" as="vid">
<my-card
card-img="[[vid.img]]"
card-name="[[vid.title]]"
card-text="[[vid.description]]"
card-key="[[vid.$key]]">
</my-card>
</template>
Second Attempt
In my second attempt I used a forEach() to store the returned data insie an array which I then added to my 'courseVidData' property.
This returns me as expected an array with three objects. Unfortunately the dom-repeat is doing nothing.
firebase.database().ref('/courseVideos/' + ckey).once('value', function(snap) {
var vidData = [];
snap.forEach(function(childSnapshot) {
let videoRef = firebase.database().ref('videos/' + childSnapshot.key);
videoRef.once('value', function(snapShot) {
vidData.push(snapShot.val());
this.courseVidData = vidData;
console.log(this.courseVidData); //returns array with the object's
}.bind(this));
});
});

So I found a way of doing it after reading through the documentation of the Polymer Template repeater (dom-repeat) And the way Arrays are handled in Polymer.
This might be not the cleanest approach but it works for now. If somebody is pointing out what could be improved I am happy to change my answer or accept a different one.
//Retrieve course videos and their details
firebase.database().ref('/courseVideos/' + ckey).on('child_added', snap => {
let videoRef = firebase.database().ref('videos/' + snap.key);
videoRef.once('value', function(snapShot) {
if (!this.courseVidData) this.set('courseVidData', []);
this.push('courseVidData', {video: snapShot.val()});
}.bind(this));
});
I can't really explain why but I had to add a if statement to check for the array and set it if not existing. Then I place the value of my snapshot inside the 'courseVidData' Property which is declared inside my Properties and from type Array.
Because the key of each returned object is now 'video' it is necessary to use [[item.video.title]] to access the object values (code below).
<template is="dom-repeat" items="[[courseVidData]]">
<h1>[[index]]</h1>
<h1>[[item.video.title]]</h1>
</template>
Update
Although this method works the unique key Firebase creates gets lost in the array. To keep the key for each object I store both key and object inside another object and append this to my array.
I know this isn't pretty and as mentioned above I am still looking for a better solution. However, it does the trick for me as a bloody beginner.
firebase.database().ref('/courseVideos/' + ckey).on('child_added', snap => {
let videoRef = firebase.database().ref('videos/' + snap.key);
videoRef.once('value', function(snapShot) {
var vidKeyObj = {key:snapShot.key, value:snapShot.val()};
if (!this.courseVidData) this.set('courseVidData', []);
this.push('courseVidData', {video: vidKeyObj});
}.bind(this));

Related

Can I use a dynamic value to access a specific array in Vue.js?

I've been trying to figure out how to do this, but can't seem to get it to work. I have created a function that is being called when I click a component using v-on:click. I am also trying to pass in a value that I can then use to access a particular array that is coming in the form of data from a backend.
Here is the function in the component:
<v-card
#click="getContent('topSellers')"
>
I am then passing the 'topSellers' value as an "id" into the function that is being used to get access the exact array that I am looking for:
getContent(id) {
this.accordion = this.data.id;
console.log("data", id);
}
First of all, this.data.topSellers works. And I am seeing that the id value is correct in the console. Obviously, this is not working because id is currently a string, but I don't know how to fix that issue. Any help would be appreciated!
You need this.data[id] instead of this.data.id.
See property accessors on MDN for reference.
data[id] will access the property of data with the name of the value of id, in your case topSellers. So data.topSellers is equivalent to data["topSellers"]
[] allows you to access objects with variables.
It is recommended to handle exceptions because the variable received is not safe.
getContent(id) {
const accordion = this.data[id] || null;
if(accordion){
console.log("data", accordion);
this.accordion = accordion;
}
}

Result is not display in autocomplete (Angular)

I use ng-prime <p-autocomplete> for display values via search in the back-end
here is my html
<p-autoComplete [(ngModel)]="agent" [suggestions]="filteredAgents" name="agents" (completeMethod)="filterAgents($event)" [size]="10"
placeholder="Agents" [minLength]="3"></p-autoComplete>
At the component.ts I initialize array like this at start of the component
filteredAgents: string[] = [];
and I have a method to send query to back end and push it to array
filterAgents(event) {
let query = event.query;
this._agentsService.getAgentSearch(query).subscribe(result => {
result.items.forEach((value) => {
this.filteredAgents.push(value.name);
console.log(this.filteredAgents);
});
});
}
I see filtered value in console, but I don't see it in suggestions.
Where can be my problem?
AutoComplete either uses setter based checking or ngDoCheck to realize if the suggestions has changed to update the UI. This is configured using the immutable property, when enabled (default) setter based detection is utilized so your changes such as adding or removing a record should always create a new array reference instead of manipulating an existing array as Angular does not trigger setters if the reference does not change. ( Angular documentation )
Array.prototype.push doesnt create a new reference it rather mutates the original array. So you need to make a new one.
filterAgents(event) {
let query = event.query;
this._agentsService.getAgentSearch(query).subscribe(result => {
this.filteredAgents = [...result.items.map(e => e.name)]
});
}
I maped the result to extract the names.
If filtered agents is an object array try adding field="name" to the directive attributes.
Here name is a field in the object. The directive uses this field to display in suggestions

How do I set returned Firebase data to a $scope variable in AngularJS?

What I am trying to do:
I want to output all of the books in the book node on my home page. What I need is an array of objects that contain all of the books in the book node so that I can loop through them using the ng-repeat directive in AngularJs. In the code below, when I console log the data, I get an object of objects, which cannot be used with ng-repeat. Another issue that I am having is when I try to output the $scope.allBooks variable on to the page, nothing appears. Can anyone help me with this?
Using Javascript SDK not Angularfire
https://firebase.google.com/docs/
// Main Controller
firebase.database().ref('books').on('value', function(snapshot) {
var data = snapshot.val();
$scope.allBooks = data;
console.log(data);
});
// Book Node in Firebase
books : {
id : {
title: 'The Hunger Games',
author: 'Suzanne'
},
id : {
title: 'Harry Potter and the Order of the Phoenix',
author: 'J.K.'
}
}
If you don't want to use angularfire you'll need to let angular know to run an update.
// Main Controller
firebase.database().ref('books').on('value', function(snapshot) {
var data = snapshot.val();
$scope.allBooks = data;
console.log(data);
$scope.$apply();
});
Just add in the $scope.$apply() to let angular know to update.
You can most certainly use objects with ng-repeat on objects with the proper syntax. This is explained on the ng-repeat documentation page.
For example:
<div ng-repeat="(key, value) in myObj"> ... </div>
But this will break the order of items, so it's definitely not a recommended approach. Instead, you should use the forEach method to iterate children in the proper order, and build the array yourself.
If you use the Firebase JS SDK directly, Angular will not be notified of the changes to your data. You must trigger a digest cycle manually, so the scopes can be re-evaluated.
One of the ways to do it is by using $timeout to wrap scope manipulations. This was named a best practice in the old Firebase documentation.
// Main Controller
firebase.database().ref('books').on('value', function(snapshot) {
var data = snapshot.val();
console.log(data);
$timeout(function() {
$scope.allBooks = data;
});
});

Updating DOM according to Firebase Object

I want to update my DOM element everytime the value on the Firebase changes. I've seen that Angularfire handles three-way data binding, but from what I understood it only works if you take elements from $firebaseArray directly from the DOM.
What I have is an Element on the DOM (chart) that depends on some of the data on a $firebaseArray, but my element gets the data from a function instead of directly from the $firebaseArray. That means I have to do some pre-processing on the $firebaseArray before my element can use it.
This is what I have:
<pie-chart ng-repeat="chart in myCtrl.charts"
data="chart.data"
options="chart.options"></pie-chart>
This is my controller:
function MyCtrl($firebaseArray) {
let myRef = new Firebase(refUrl);
let chartsFirebase = $firebaseArray(myRef);
let getCharts = function() {
let charts = [];
distanceGoals.$loaded().then(function() {
// push some things from chartsFirebase on the charts array
charts.push({
options: { ... },
data: [ ... ]
});
}
return charts;
}
this.charts = getCharts();
}
Turns out that in this way this.charts is only updated one time, after modifications on the data in Firebase I have to refresh the browser.
Has anyone an idea of what I could do to achieve this behavior?
You can add a child-changed event listener to your ref like this:
// Get a reference to our posts
var ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");
// Get the data on a post that has changed
ref.on("child_changed", function(snapshot) {
var changedPost = snapshot.val();
console.log("The updated post title is " + changedPost.title);
});
This will get called everytime something changes in the location you put the listener on.
For more info take a look at https://www.firebase.com/docs/web/guide/retrieving-data.html and https://www.firebase.com/docs/web/api/query/on.html.
From the AngularFire documentation on $loaded()(emphasis mine):
Returns a promise which is resolved when the initial array data has been downloaded from the database.
That explains the behavior you're seeing.
To solve this, you should extend the $firebaseArray as documented here: https://www.firebase.com/docs/web/libraries/angular/guide/extending-services.html#section-firebasearray
Some related questions:
AngularFire extending the service issue
Joining data between paths based on id using AngularFire (includes a full example by the author of AngularFire)

Firebase understanding snapshot.child()

consider this data structure referenced on the Firebase quick start guide (here)
{"name": {"first": "Fred","last": "Flintstone"}
The docs say that one can access the datasnapshot location of each child object of "name" returned from a query using:
var ref = new Firebase("https://docs-examples.firebaseio.com/samplechat/users/fred");
ref.once("value", function(snapshot) {
var nameSnapshot = snapshot.child("name");
var name = nameSnapshot.val();
name === { first: "Fred", last: "Flintstone"}
var firstNameSnapshot = snapshot.child("name/first");
var firstName = firstNameSnapshot.val();
firstName === "Fred"
var lastNameSnapshot = snapshot.child("name").child("last");
var lastName = lastNameSnapshot.val();
lastName === "Flintstone"
var ageSnapshot = snapshot.child("age");
var age = ageSnapshot.val();
age === null (because there is no "age" child in the data snapshot)
});
But what's a little weird about this is when the following lines are processed.
var nameSnapshot = snapshot.child("name");
var name = nameSnapshot.val();
name.first, and name.last are also retrieved. So why would one use this snapshot method "child()"? Or rather when would it be beneficial to use this method, since when you pull the parent object, Firebase pulls all children, or is there a way to retrieve a parent node/object without pulling some of it's children? Then this method to me would make sense.
Any information would be gratefully appreciated! Thanks
is there a way to retrieve a parent node/object without pulling some of it's children?
The Firebase JavaScript API always retrieves the complete node. So: no, there isn't a way in the JavaScript API to get a shallow result/
why would one use this snapshot method child()?
If we compare snapshot.child("property") with snapshot.val().property. The DataSnapshot.child() method returns a DataSnapshot, from which you can get a ref again. The val() method deserializes the snapshot's value into JSON. So you'll have to construct your own ref if you'd need one. But the value of each depends highly on your use-case, so "why" is not something I can answer for you.
The snapshot is a exact picture of everything in the node at the time of the call. What it contains however, will vary depending on how you get the snapshot.
The example provided in the guide is slightly one-dimensional. In general you would not have a node called name with just one person listed.
A better example would be a node called users with data as such
users
user_id_0
firstName:
lastName:
age:
user_id_1
firstName:
lastName:
age:
When used with the Value parameter, the snapshot of the users node contains all of the node's children and all of the data within each child (in this case, all of the users and their data), and the block that handles it is called once. We use this to read, for example, all of the users in the users node and then iterate over the users for some specific data. We also use it to do multi-parameter queries, which are not supported directly by firebase. So for example, we want to get all users named Elmo, age 20.
The Add parameter reads each child, one at a time, calling the block once for child, which would be each user in this case. Typically we use this to keep UI tableView's updated (ObjC) so when a new child data is added to Firebase, all of the apps who are observing will be notified, so we can then update our UI table.
You cannot retrieve a parent object without also retrieving the children. However, you can directly access a child if you know the parent object if you are looking for a specific piece of data. So you could retrieve users/user_id_0/age

Categories

Resources