42

I am trying to query my database such that it retrieves an ordered list based on a child key. I do it as follows (see below), but nothing happens, meaning that it returns an object ordered exactly in the same way as it is stored in the Firebase database. What is going on?

self.getAllProfiles = function () {
    var qProfile = $q.defer();
    var ref = new Firebase(FBURL);
    ref.child("users").orderByChild('last_update').on("value", function (snapshot) {
        console.log(snapshot.val()) // HERE IS WHERE IT SHOULD BE ORDERED
        qProfile.resolve(snapshot.val());
    }, function (errorObject) {
        qProfile.reject(errorObject);
    });
    return qProfile.promise;
};

To add, my users node looks as follows:

users
   /$username
       /last_update
       /id
       /data
          /profile_image
          /display_name

Here is a snapshot:

Tester: Object
   github: Object
   last_update: 1447732462170
   userId: "github:12345"
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
WJA
  • 6,676
  • 16
  • 85
  • 152
  • Can you show your data unordered and then what it looks like when it comes back as a snapshot? – David East Nov 24 '15 at 14:19
  • Updated the question with the snapshot – WJA Nov 24 '15 at 14:37
  • 1
    It's hard to tell what's going on with the data you have provided. Can you create a JSBin that demonstrates your issue? That would make it much easier to troubleshoot what's going on. – David East Nov 24 '15 at 14:59
  • Likely answer below. But as David said: consider creating a JSBin/JsFiddle next time, since it'll provide with a minimal, complete example. One thing now missing is enough data to understand what you're seeing and why that might be (the always tricky balance between 'minimal' and 'complete'). – Frank van Puffelen Nov 24 '15 at 15:14
  • Make sure that you're actually looping through the snapshot children. If you just print the snapshot it's ordered like standard JSON and not the way it came back from the firebase server. – teradyl Jul 13 '17 at 06:32

4 Answers4

103

When you call snapshot.val(), you are getting back a JSON object. The order of keys in a JSON object is determined by your browser and not by Firebase.

To get the children in order use the built-in forEach method of the snapshot:

self.getAllProfiles = function () {
    var qProfile = $q.defer();
    var ref = new Firebase(FBURL);
    ref.child("users").orderByChild('last_update').on("value", function (snapshot) {
        snapshot.forEach(function(child) {
            console.log(child.val()) // NOW THE CHILDREN PRINT IN ORDER
        });
        qProfile.resolve(snapshot.val());
    }, function (errorObject) {
        qProfile.reject(errorObject);
    });
    return qProfile.promise;
};

You can leave the q.resolve() call where it is: snapshot.forEach() is not an asynchronous call.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 19
    So if we need the data sorted we have to create a new object with new keys or put each child.val() in an array. Isn't that terribly inefficient? – Pier Jun 17 '16 at 13:33
  • 4
    Would you mind answering @Pier 's question? I have the same question. – Onichan Jul 20 '16 at 07:19
  • 1
    @Pier You don't need to if you use other eventTypes, please see my answer for detail. – user2875289 Dec 08 '16 at 04:16
  • 2
    @Onichan Please see my answer. – user2875289 Dec 08 '16 at 04:17
  • 7
    I feel that they need to add this caveat into the official docs, because the fact that I had the query written out correctly, no errors but still getting the data in the order that's in the database stumped me until I came across this answer. – Hayko Koryun Mar 30 '17 at 15:34
  • 2
    You mean like the example in the docs here? https://firebase.google.com/docs/database/web/lists-of-data#listen_for_value_events – Frank van Puffelen Mar 31 '17 at 00:06
  • Just a note for future reference, I faced an issue that return type should be false, more info here: https://stackoverflow.com/questions/39845758/argument-of-type-snap-datasnapshot-void-is-not-assignable-to-parameter-o – frunkad Feb 19 '19 at 19:42
  • 2
    wow, I was using orderByChild in 21 places in my project for the last 2 years and just figured out that I was doing it wrong... (after a user reported a bug and after further digging). Seems like it's time for me to create a wrapper like getOrderedResults(snapshot) { var obj = {} snapshot.forEach(function(child) { obj[child.key] = child.val() }); return obj } – vir us Jul 25 '19 at 22:00
  • 1
    I came across the same problem/solution in my Unity/C# implementation: One needs to iterate `args.Snapshot.Children` to get ordered values, not `args.Snapshot.GetValue` – cjcurrie Apr 25 '21 at 18:51
20

I know this question has been answered and is more than 1 year old, but since there are still some confusion in the comment section, I would like to add some information.

The problem

The original problem is that the OP want to retrieve an ordered list based on a child key from Firebase realtime database, but the .orderByChild('arg') does not work as expected.

But what didn't work as expected is not .orderByChild('arg'), but .on("value", callback). Because .on("value", callback) works a bit of different from other eventTypes like .on("child_added", callback).

Example

Say we have a firebase realtime database as below:

{
    myData: {
        -KYNMmYHrzLcL-OVGiTU: {
             NO: 1,
             image: ...
        },
        -KYNMwNIz4ObdKJ7AGAL: {
             NO: 2,
             image: ...
        },
        -KYNNEDkLpuwqQHSEGhw: {
             NO: 3,
             image: ...
        },
    }
}

--

If we use .on("value", callback), the callback() will be called 1 time, and return an Object Array of 3 objects.

ref.on("value", function(snapshot) {
    console.log(snapshot.val());
    // Please see Frank van Puffelen's answer
}

enter image description here

--

If we use .on("child_added", callback), the callback() will be called 3 times, each time returns an Object, and they are returned in order.

ref.once("child_added", function(snapshot) { 
    console.log(snapshot.val());
    // The objects are returned in order, do whatever you like
}

enter image description here

Conclusion

If you only need to fetch ordered data from firebase (e.g. to initialize UI.) Then ref.orderByChild('arg').once("child_added", callback) suits you well, it is simple and easy to use.

However, if for some reason you need to use ref.orderByChild('arg').on("value", callback), then please see Frank van Puffelen's answer.

Reference

Please read Firebase Document for more information about on(eventType, callback, cancelCallbackOrContext, context), their arguments and their return values.

Another useful document: Work with Lists of Data on the Web

user2875289
  • 2,799
  • 1
  • 22
  • 25
  • 3
    The real reason you see difference is that the type of the FIB query result is 'dictionary' where the 'key' (id) of the record is the 'key' of the dictionary. Hence, JS (browser) sorts this list by its key. That is why if u query as 'child_added' (one by one) or use 'snapshot.forEach' you'll workaround this phenomena. So that's more of JS thing rather than FIB thing. @frank-van-puffelen's answer is more correct for that reason. – Elya Livshitz Dec 13 '17 at 09:03
  • @user2875289 I am using nativescript+angular and it is giving me error when I use Frank's solution. `TypeError: snapshot.forEach is not a function` – Robert Williams Jun 24 '18 at 06:23
6

For ordering using the value event listener:

firebase.database().ref('/invoices').orderByChild('name').on('value', snapshot => {
    snapshot.forEach(child => {
        console.log(child.key, child.val());
    });
}

If you want to reverse the order, try:

function reverseSnapshotOrder (snapshot) {
  let reversed = [];

  snapshot.forEach(child => {
    reversed.unshift(child);
  });

  return reversed;
}

reverseSnapshotOrder(snapshot).forEach(child => {
  console.log(child.key, child.val());
});

Please see: https://firebase.google.com/docs/database/web/lists-of-data#listen_for_value_events

Renan Coelho
  • 1,360
  • 2
  • 20
  • 30
0

I struggled with the same problem in iOS. If you convert snapshot to NSDictionary object, it converts to unordered list. Objective-c version in case of need:

[[self.refChild queryOrderedByChild:@"date_created"] 
  observeEventType:FIRDataEventTypeValue 
         withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {

     for (FIRDataSnapshot *child in snapshot.children) {  

          NSDictionary *anObject = child.value; 
          NSString *aKey         = child.key;      
     } 
}];
Muhammed Tanriverdi
  • 3,230
  • 1
  • 23
  • 24