Я работаю над своим первым проектом Firebase, используя AngularFire2. Ниже приведен общий дизайн моего учебного проекта.

  • Пользователи загружают фотографии, и они сохраняются в хранилище Firebase как изображения.
  • Загруженные фотографии перечислены на главной странице, отсортированной по отметке времени.

Ниже структура, которая у меня есть сейчас, когда я начал. Но я испытываю трудности при выполнении соединений. Я должен иметь возможность получать информацию о пользователях для каждой загрузки и сортировать загрузки по отметке времени.

User:
- Name
- Email
- Avatar

Uploads:
  - ImageURL
  - User ID
  - Time

Я прочитал несколько блогов, в которых нарушается нормализация структуры данных. Как лучше всего перемоделировать структуру базы данных для моего данного сценария?

Любой пример создания некоторых примеров данных в новом предложенном решении будет отличным для моего понимания.

Как только загрузка изображения будет завершена, я вызываю приведенный ниже код для создания записи в базе данных.

addUpload(image: any): firebase.Promise<any> {
  return firebase.database().ref('/userUploads').push({
    user: firebase.auth().currentUser.uid,
    image: image,
    time: new Date().getTime()
  });
}

Я пытаюсь объединить 2 лица, как показано ниже. я не уверен, как я могу сделать это эффективно и правильно.

 getUploads(): any {
    const rootDef = this.db.database.ref();
    const uploads = rootDef.child('userUploads').orderByChild('time');

    uploads.on('child_added',snap => {
      let userRef =rootDef.child('userProfile/' + snap.child('user').val());
      userRef.once('value').then(userSnap => {
        ???? HOW TO HANDLE HERE
      });
    });

return ?????;
}

Я хотел бы получить окончательный список, содержащий все данные о загрузке и соответствующие данные пользователя для каждой загрузки.

1
Purus 27 Май 2017 в 21:16

2 ответа

Лучший ответ

Я сделал следующее, основываясь на ответе Фрэнкса, и это работает. Я не уверен, что это лучший способ для работы с большим количеством данных.

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);
    });

}
0
Purus 29 Май 2017 в 14:13

Этот тип соединения всегда будет сложным, если вы пишете это с нуля. Но я постараюсь провести вас через это. Я использую этот JSON для моего ответа:

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

Здесь мы используем трехступенчатый подход:

  1. Загрузить данные из uploads
  2. Загрузите пользователей для этих данных из users
  3. Присоедините данные пользователя к данным загрузки

1. Загрузите данные uploads

Ваш код пытается вернуть значение. Поскольку данные загружаются из Firebase асинхронно, они еще не будут доступны при выполнении вашего оператора return. Это дает вам два варианта:

  1. Передайте обратный вызов getUploads(), который вы затем вызовете после загрузки данных.
  2. Вернуть обещание от getUploads(), которое разрешается после загрузки данных.

Я собираюсь использовать обещания здесь, так как код уже достаточно сложен.

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

Это должно быть достаточно читабельно: мы загружаем все загрузки и, как только они загружаются, мы возвращаем значение.

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

Напечатаем:

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

2. Загрузите пользователей для этих данных из users

Теперь на следующем шаге мы будем загружать пользователя для каждой загрузки. Для этого шага мы больше не возвращаем загрузки, просто пользовательский узел для каждой загрузки:

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

Вы можете видеть, что мы зацикливаемся на загрузках и создаем обещание для загрузки пользователя для этой загрузки. Затем мы возвращаем Promise.all(), что гарантирует, что его then() вызывается только после загрузки всех пользователей.

Сейчас звоню

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

Печать:

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

Таким образом, мы получаем массив пользователей, по одному для каждой загрузки. Обратите внимание, что если бы один и тот же пользователь опубликовал несколько загрузок, вы бы получили этого пользователя несколько раз в этом массиве. В реальном производственном приложении вы хотите дублировать пользователей. Но этот ответ уже становится достаточно длинным, поэтому я оставляю это как упражнение для читателя ...

3. Присоедините данные пользователя к данным загрузки.

Последний шаг - взять данные двух предыдущих шагов и соединить их вместе.

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;
    });
  });
}

Вы увидите, что мы добавили then() к вызову Promise.all(), который вызывается после загрузки всех пользователей. На данный момент у нас есть пользователи и их загрузки, поэтому мы можем присоединиться к ним вместе. И поскольку мы загружали пользователей в том же порядке, что и загрузки, мы можем просто присоединить их по их индексу (i). Как только вы удалите дубликаты пользователей, это будет немного сложнее.

Теперь, если вы вызываете код с помощью:

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

Он печатает:

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

Массив загрузок с именем пользователя, который создал эту загрузку.

Рабочий код для каждого шага находится в https://jsbin.com/noyemof/edit?js, консоль

2
Frank van Puffelen 28 Май 2017 в 15:14