Let's say I have a collection of articles:
var articlesRef = new Firebase("https://example.firebaseio.com/articles");
$scope.articles = $firebase(articlesRef);
I want to add a few articles with a new Date() priority:
$scope.articles.$add(newArticle1).$priority = new Date();
$scope.articles.$add(newArticle2).$priority = new Date();
$scope.articles.$add(newArticle3).$priority = new Date();
How do I fetch these articles and sort by the Date priority?
How do I query a range of articles from X-Date to Y-Date?
How do I retrieve the values of those priorities?
If you are trying to order posts by time, I recommend not using Date() and only rely on $add. $add automatically creates chronologically ordered lists of items. In the absence of priorities, Firebase automatically orders items by their key names.
To fetch the articles in chronological order, simply use the orderByPriority filter, eg: <ul ng-repeat="article in articles | orderByPriority">.
Querying a range from a specific data becomes trickier - if you know the key name for the article you want to start the range at, you can just pass that to startAt. $add returns a Firebase reference, and you can get the key name via .name().
For example, the following snippet will create a window starting at the article just added and upto 10 articles after it:
var justAdded = $scope.articles.$add(newArticle);
var query = ref.startAt(justAdded.name()).limit(10);
For a comprehensive solution where you need to be able to query by any time across all articles, you might consider storing a time for each item added via $add. I'd again avoid using Date() becaue client-side times cannot be trusted, but instead use Firebase's server side timestamps: https://www.firebase.com/docs/javascript/servervalue/TIMESTAMP.html which are more reliable. For instance, you might store the time at which a particular article was added so you can later cross-reference a given time with an article ID auto-generated via $add.
Related
I am trying to create a trending page on my application. I want trending posts to be based on how many likes the post has and how recent the post is. The more recent with more likes will get it to be listed first. I want something like this:
let startDate = moment();
startDate.subtract(3, "days");
snapshot = await db
.collection("/vents/")
.where("server_timestamp", ">", startDate.valueOf())
.orderBy("like_counter")
.limit(10)
.get();
This obviously does not work because you can only orderBy the same field as the where clause. Does anybody know how to do this? If I have to use data aggregation to create a trending property on posts, I could do that but it would be a lot of code and would kind of suck to write all that out.
If I have to use data aggregation here is roughly how I would do it.
On each comment and like in the database, I would set up a listener to increase or decrease the trending attribute based on the new like or comment received and how old the post was. I would also need a pubscheduler to go through all posts and update them at least once a day to update the trending score attribute even if the post has not received a like or comment. I could also pull the top 20 trending posts once a day instead of all the posts and just update those as likely every other post in the database that is not trending would not likely need updating.
Please let me know your thoughts on if I should use data aggregation for this, or if I should build out my query.
looking at your code you are write you need to orderBy the same field as the where clause, but if you have a filter with a range comparison (<, <=, >, >=) your first ordering must be on the same field, the tricky thing is that you can use more that one orderBy. An example of what I am saying could be:
let startDate = moment();
startDate.subtract(3, "days");
snapshot = await db
.collection("/vents/")
.where("server_timestamp", ">", startDate.valueOf())
.orderBy("server_timestamp")
.orderBy("like_counter")
.limit(10)
.get();
I use this documentation to get to the code that I am sharing with you.
I'm using react and firebase real-time database. I want to create a list for each user. A list of id to later look for files in storage. In this case what is the best method to generate and add an id to list in database? (Not list of data, the data stored in the database is the list)
So I have two methods in mind. I'll write in pseudo code
//Method 1
//get id list
receivedList = getKeyListFromDB()
//generate ID
newID = generateID()
// append new id
receivedList.append(newID)
//set value with updated list
updateDB(receivedList)
//Method 2
// Using the list provided by the firebase
newRef = firebase.database().ref().child('pathToList').push()
newRef.set({
key: newRef.key
})
I was going for method 1, but couldn't find a way to generate id. And retreiving the list to update it seemed inefficient. So I tried to find another method. But I can't find a good way to do this.
Solution to add a value to list in database
I have solved using transaction method. But if there is a way to solve by using push please do answer.
There are many reasons why Firebase has a built-in push() operation to add a child node to a list. In fact, you've already listen one of them in your question:
retreiving the list to update it seemed inefficient
Other reasons are that push IDs will work while your app is offline, and that they scale much better across multiple users.
If you want to learn more about why you should use push(), read Best Practices: Arrays in Firebase.
First, you can generate a unique key by using below code
const newID = firebase.database.ref().push().key
According to this answer, whenever you use push on a Database Reference, a new data node is generated with a unique key that includes the server timestamp. These keys look like -KiGh_31GA20KabpZBfa.
Because of the timestamp, you can be sure that the given key will be unique, without having to check the other keys inside your database.
Second, you can add an item to a list in database using transaction.
const newID = firebase.database.ref().push().key
//user is user object from firebase auth module
const newDatabaseRouteRef = firebase.database.ref().child('users/' + user.uid + '/ids')
// if null, create a new list with id, if not add newID to list
newDatabaseRouteRef.transaction((currentValue) => {
return currentValue ? currentValue.push(newID) : [newID]
})
I'm creating another answer just to show different kinds of solutions. This solution, using push is better than using transaction.
// Get a new ref by push
const newDatabaseRouteRef = firebase.database.ref().child('users/' + user.uid + '/routes').push()
// Set the value to the key of received ref
newDatabaseRouteRef.set(newDatabaseRouteRef.key)
This is better because
Transaction needs multiple round trips where push() doesn't, thus reducing time.
Is more safe according to this article.
This is achieves the same result because even using list, when the list is saved in firebase database, it is stored as object. ex) ["a","b","c"] => {0:"a",1:"b",2:"c"}
Since recently, Firestore returns timestamps not as Date objects, but as Timestamp objects. Meaning, after every query you have to do something like this:
// Old:
const date = snapshot.get('created_at');
// New:
const timestamp = snapshot.get('created_at');
const date = timestamp.toDate();
I have objects with multiple timestamps stored in them, and I am forced to convert every of these timestamps individually before I can do anything with my object. What I do now is the following:
let user = // get user from firestore
user.created = user.created.toDate()
user.lastUpdated = user.lastUpdated.toDate()
user.foo.foo.foo.foo.fooDate1 = user.foo.foo.foo.foo.fooDate1.toDate()
user.foo.foo.foo.fooDate1 = user.foo.foo.foo.fooDate1.toDate()
...
user.foo.fooDate99 = user.foo.fooDate99.toDate()
Is there a way around that?
I have to do this with all of my documents, even if I don't need the timestamps to be converted, since various dependencies (e.g. devalue) break with a "Non-POJO" errors when there are raw firestore timestamps in my object.
Is there an option to directly convert all timestamps of a document to normal Date objects, or is the manual way the only option?
There's no simple option you can toggle to make this happen.
If you don't want to manually change them all, you could write some code to recursively descend all the properties of all your objects, check which ones look like Timestamp objects (for example, it has a toDate function), and make the conversion if so.
Ok, so I've been reading and reading and searching and searching and strangely it doesn't seem like my scenario has been really covered anywhere.
I have an app that creates a list of products. I want a simple view that can sort the products and page through them.
Fore reference here is a simple representation of the data in Firebase.
app
stock
unique_id
name
url
imageUrl
price
When creating the list I have multiple threads using the push method on my firebase references:
new Firebase(firebaseUrl).child('stock').push({
name: "name",
price: 123
});
This gives me a lovely "hash" collection on the stock property of the app.
So what I'd now like to do is have a table to sort and page through the records that were placed in the stock hash.
I make a GET request to my server to a url like /stock?limit=10&skip=10&sort=name%20asc. This particular url would be the second page where the table contained 10 records per page and was sorted by the name property in ascending order.
Currently in my query handler I have this:
var firebaseRef = new Firebase(firebaseUrl).child('stock');
if (this.sortDesc) {
firebaseRef = firebaseRef
.orderByChild(this.sortProperty)
.endAt()
.limitToFirst(this.limitAmount);
} else {
firebaseRef = firebaseRef
.orderByChild(this.sortProperty)
.limitToFirst(this.limitAmount);
if (this.skipAmount > 0) {
firebaseRef = firebaseRef.startAt(this.skipAmount);
}
}
firebaseRef.once('value', function (snapshot) {
var results = [];
snapshot.forEach(function (childSnapshot) {
results.push(childSnapshot.val());
});
callback(null, results);
});
I'm running into a couple of problems. I'm going to split this into two cases, ascending and descending queries.
Ascending query
The orderByChild and limitToFirst seems to work correctly in the sorting ascending case. This means I can change which property has an ascending sort and how many results to return. What I am not able to get to work is skipping n records for paging to work. In the example query above I'm going to the second page. I do not get results 11-20, but I instead get the same 10 records as the first page.
Descending query
In this case I cannot begin to figure out how to tell Firebase to order by a property of the object identified by the unique key in a descending fashion. The closest I've read is to use endAt() and then limit. Docs say the limit is deprecated plus this still doesn't help me with any paging.
I tired to do doodles picturing how this would work. I came up with: order by the property, start at the 'end' of the collection, and then limit back to the page size. While this still wouldn't solve paging I would expect it to give me the last n records where n was the size of the page. I get no results.
I suppose I could say use firebaseRef = firebaseRef .orderByChild(this.sortProperty).limitToLast(this.limitAmount + this.skipAmount); and in the result callback use the forEach loop to take the first (or would it be the last; I'm not sure how that iteration would work) n records where n=this.limitAmount. This just seems inefficient. Wouldn't it be better to limit the query instead of using CPU cycles to limit data that had come over the wire or is this the relational DB query thought pattern overriding the correct thought process for NoSQL?
Further Confusion
After posting this I've still been working on a solution. I've had some things get close, but I'm also running into this filtering issue. How could I filter a set of items to one property by still sorting on another? Jeez! I want to have the ability for a user to get all the stock that isn't sold out and order it by price.
Finally
Why hasn't this basic example been fleshed out on any of the Firebase "Getting Started" pages? Being able to show tabular data, page through it, sort, and filter seem like something that EVERY web developer would come across. I'm using ng-table in an Angular app to drive the view, but it still seems that regardless of platform that the queries that I'm trying to generate would be practical on any platform that Firebase supports. Perhaps I'm missing something! Please educate me!
Firebase and NoSQL
I've come up with this simple scenario that I often run into with web applications. I want to show tabular data, filter, page, and sort it. Very simple. Very common. Writing a SQL statement for this would be dead easy. Why is the query so complicated for something like Firebase. Is this common with all NoSQL solutions? There is no relational data being stored thus the need for a relational database seems unnecessary. Yet, it seems like I could hack together a little flat file to do this storage since the ability to make Firebase do these simple tasks is not made clear in its API or Docs. FRUSTRATED!!!
I am using local storage to store user entries and am displaying the entries on another page. I need a way to sort them based on the most recent date and time of edit. Is there a way to do this with HTML5. If not, what's the easiest/most effective way to do so?
Thanks for the inputs.
If your keys/values have an inherent order to them (alphabetical, numerical, etc), then putting a timestamp in them may be superfluous. Although the Storage object has no sort method, you can create a new Array() and then sort that.
function SortLocalStorage(){
if(localStorage.length > 0){
var localStorageArray = new Array();
for (i=0;i<localStorage.length;i++){
localStorageArray[i] = localStorage.key(i)+localStorage.getItem(localStorage.key(i));
}
}
var sortedArray = localStorageArray.sort();
return sortedArray;
}
The disadvantage to this is that the array is not associative, but that is by nature of the JavaScript Array object. The above function solves this by embedding the key name into the value. This way its still in there, and the functions you'd use to display the sorted array can do the footwork of separating the keys from the values.
You've got to pair the timestamp with the stored value somehow, you can create a wrapper object for each value and store the value and the timestamp in a single object. Assuming you have a value myvalue you want to store with reference myref:
var d=new Date();
var storageObject = {};
storageObject.value = myvalue;
storageObject.timestamp = d.getTime();
localStorage.setItem(myref, JSON.stringify(storageObject));
On the other page you then need to rehydrate your objects into an array and implement your compareFunction function.
Your other option would be to use Web SQL Database and Indexed Database API which lets you more naturally store and query this sort of multifaceted info, but you would probably have to create some sort of abstract wrapper to make things work easily cross browser.