Skip to content Skip to sidebar Skip to footer

Firebase Real Time Database Structure For File Upload

I am working on my first Firebase project using AngularFire2. Below is the overall design of my learning project. Users uploads photos and it's stored in the Firebase storage as i

Solution 1:

This type of join will always be tricky if you write it from scratch. But I'll try to walk you through it. I'm using this JSON for my answer:

{
  uploads: {
    Upload1: {
      uid: "uid1",
      url: "https://firebase.com"
    },
    Upload2: {
      uid: "uid2",
      url: "https://google.com"
    }
  },
  users: {
    uid1: {
      name: "Purus"
    },
    uid2: {
      name: "Frank"
    }
  }
}

We're taking a three-stepped approach here:

  1. Load the data from uploads
  2. Load the users for that data from users
  3. Join the user data to the upload data

1. Load the data uploads

Your code is trying to return a value. Since the data is loaded from Firebase asynchronously, it won't be available yet when your return statement executes. That gives you two options:

  1. Pass in a callback to getUploads() that you then call when the data has loaded.
  2. Return a promise from getUploads() that resolves when the data has loaded.

I'm going to use promises here, since the code is already difficult enough.

function getUploads() {
  return ref.child("uploads").once("value").then((snap) => {
    return snap.val();
  });
}

This should be fairly readable: we load all uploads and, once they are loaded, we return the value.

getUploads().then((uploads) => console.log(uploads));

Will print:

{
  Upload1 {
    uid: "uid1",
    url: "https://firebase.com"
  },
  Upload2 {
    uid: "uid2",
    url: "https://google.com"
  }
}

2. Load the users for that data from users

Now in the next step, we're going to be loading the user for each upload. For this step we're not returning the uploads anymore, just the user node for each upload:

function getUploads() {
  return ref.child("uploads").once("value").then((snap) => {
    var promises = [];
    snap.forEach((uploadSnap) => {
      promises.push(
         ref.child("users").child(uploadSnap.val().uid).once("value")
      );
    });
    return Promise.all(promises).then((userSnaps) => {
      return userSnaps.map((userSnap) => userSnap.val());
    });
  });
}

You can see that we loop over the uploads and create a promise for loading the user for that upload. Then we return Promise.all(), which ensures its then() only gets called once all users are loaded.

Now calling

getUploads().then((uploads) => console.log(uploads));

Prints:

[{
  name: "Purus"
}, {
  name: "Frank"
}]

So we get an array of users, one for each upload. Note that if the same user had posted multiple uploads, you'd get that user multiple times in this array. In a real production app you'd want to de-duplicate the users. But this answer is already getting long enough, so I'm leaving that as an exercise for the reader...

3. Join the user data to the upload data

The final step is to take the data from the two previous steps and joining it together.

function getUploads() {
  return ref.child("uploads").once("value").then((snap) => {
    var promises = [];
    snap.forEach((uploadSnap) => {
      promises.push(
         ref.child("users").child(uploadSnap.val().uid).once("value")
      );
    });
    return Promise.all(promises).then((userSnaps) => {
      var uploads = [];
      var i=0;
      snap.forEach((uploadSnap) => {
        var upload = uploadSnap.val();
        upload.username = userSnaps[i++].val().name;
        uploads.push(upload);
      });
      return uploads;
    });
  });
}

You'll see we added a then() to the Promise.all() call, which gets invoked after all users have loaded. At that point we have both the users and their uploads, so we can join them together. And since we loaded the users in the same order as the uploads, we can just join them by their index (i). Once you de-duplicate the users this will be a bit trickier.

Now if you call the code with:

getUploads().then((uploads) => console.log(uploads));

It prints:

[{
  uid: "uid1",
  url: "https://firebase.com",
  username: "Purus"
}, {
  uid: "uid2",
  url: "https://google.com",
  username: "Frank"
}]

The array of uploads with the name of the user who created that upload.

The working code for each step is in https://jsbin.com/noyemof/edit?js,console


Solution 2:

I did the following based on Franks answer and it works. I am not sure if this is the best way for dealing with large number of data.

getUploads() {

    return new Promise((resolve, reject) => {
      const rootDef = this.db.database.ref();
      const uploadsRef = rootDef.child('userUploads').orderByChild('time');
      const userRef = rootDef.child("userProfile");
      var uploads = [];

      uploadsRef.once("value").then((uploadSnaps) => {

        uploadSnaps.forEach((uploadSnap) => {

          var upload = uploadSnap.val();

          userRef.child(uploadSnap.val().user).once("value").then((userSnap) => {
            upload.displayName = userSnap.val().displayName;
            upload.avatar = userSnap.val().avatar;
            uploads.push(upload);
          });
        });

      });

      resolve(uploads);
    });

}

Post a Comment for "Firebase Real Time Database Structure For File Upload"