Получение документов firestore из массива ссылок на документы

#javascript #angular #firebase #google-cloud-firestore #rxjs

#javascript #angular #firebase #google-облако-firestore #rxjs

Вопрос:

В настоящее время я работаю над довольно сложным набором запросов к firestore.

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

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

Порядок следующий: выполните запрос для всех документов во tags вложенной коллекции, который обрабатывается приведенной ниже функцией:

 getTagsOnPage(workspaceId: string, pageId: string) {
    // get all of the references in the tags sub-collection, and puts them in an array
    // get all id's, do not get data
    return this.afs
      .collection("users")
      .doc(`${auth().currentUser.uid}`)
      .collection<Workspace>("workspaces")
      .doc(`${workspaceId}`)
      .collection<Page>("pages")
      .doc(`${pageId}`)
      .collection("tags")
      .snapshotChanges()
      .pipe(
        map((actions) => {
          return actions.map((a) => {
            const ref = a.payload.doc.get("reference");
            return ref; // return an array of (id: reference) key-value pairs
          });
        })
      );
  }
  

Это отлично работает со следующим кодом, выполняющим подписку:

 this.pageService
      .getTagsOnPage(this.workspaceId, this.currentPage.id)
      .subscribe((data) => {
        temp = data;
      });
  

data заключается в следующем, через консоль:

 (3) ["/users/ucB5cF4Pj3PWhRn10c9mvqQbS7x2/workspaces/It1…1tSnPI5GJrniY82vZL/localTags/1p5Tscwn14PyK6zRaFHX", "/users/ucB5cF4Pj3PWhRn10c9mvqQbS7x2/workspaces/It1tSnPI5GJrniY82vZL/localTags/lFKoB0jngmtnALut2QS2", "/users/ucB5cF4Pj3PWhRn10c9mvqQbS7x2/workspaces/It1tSnPI5GJrniY82vZL/localTags/r6sf2SX6v87arU2rKsD5"]
  

Теперь, чтобы выполнить следующий набор операций чтения данных, начинается моя путаница.

Мой первоначальный подход состоял в том, чтобы попробовать цикл for (для длины этого массива), но это потребовало бы повторения выполнения нескольких вложенных подписок, что, я думаю, невозможно в этом смысле.

Я довольно новичок в rxjs и использовал только операторы map и switchMap. В этом случае я предполагаю, что использовал бы что-то вроде mergeMap и / или flatMap, , но, честно говоря, я не уверен, как заставить это работать в данном случае. Кроме того, работа с циклом for, где мне нужно собирать документы на основе массива documentReferences, который я получаю, также приводит меня к циклу.

Это моя текущая реализация, которая есть повсюду; Я надеюсь, что вы понимаете, что я пытаюсь сделать. В принципе, получите массив ссылок через getTagsOnPage, дождитесь завершения observable, используйте switchMap, чтобы взять массив данных и выполнить цикл по нему; в идеале, подписаться на каждую ссылку и добавить в TagData, а затем вернуть это:

 let tagData;
    this.pageService.getTagsOnPage(this.workspaceId, this.currentPage.id).pipe(
      switchMap((data) => {
        let references = data;
        for (let j = 0; j < references.length; j  ) {
          let ref = this.afs.doc(`${references[j]}`);
          ref.snapshotChanges().pipe(
            map((actions) => {
              const data = actions.payload.data();
              tagData.push(data);
            })
          );
          // push the data (different data var)
        }
      })
    );
    return tagData;
  

Беспорядочно, я знаю, но я думаю, что как только я узнаю правильные операторы для использования, это будет иметь намного больше смысла.

Кроме того, atm this возвращает пустой массив. При использовании switchMap возникает ошибка, в которой говорится следующее:

 Argument of type '(data: any[]) => void' is not assignable to parameter of type '(value: any[], index: number) => ObservableInput<any>'.
  Type 'void' is not assignable to type 'ObservableInput<any>'.

  

Спасибо за любую помощь!

Комментарии:

1. combineLatest может быть правильным оператором для вашего случая. Что вы можете сделать, так это map getTagsOnPage результаты для this.afs.doc().snapshotChanges() и передать их этому оператору.

Ответ №1:

Причина вашей ошибки при использовании switchMap заключается в том, что вы не возвращаете наблюдаемое значение.

При использовании любого из «Операторов отображения более высокого порядка» (switchMap, concatMap, mergeMap и т.д.), предоставленная функция должна возвращать наблюдаемое. Поскольку ошибка гласит: «Тип void не может быть присвоен типу ObservableInput<any> «, вы ничего не возвращаете (void).

Следующая не совсем правильная вещь заключается в том, что в вашем цикле вы ссылаетесь ref.snapshotChanges().pipe() , но никогда не подписываетесь на него. Как вы, возможно, знаете, наблюдаемые объекты являются ленивыми и не будут запускаться, если нет подписчика.

Пока вы возвращаете observable внутри switchMap() , он будет автоматически подписываться на него и выдавать его значения.

Давайте подумаем об этом немного по-другому; вместо того, чтобы перебирать результаты вашего первого вызова, выполняйте их, а затем помещайте значения в массив. Вместо этого мы можем взять результаты и превратить их в наблюдаемый поток, который выдает все результаты их отдельных вызовов и объединяет их в массив для вас. Но … с небольшим отличием: я предлагаю не создавать отдельный tagData массив вне потока, а заставить ваш observable возвращать tagData форму, которая вам нужна в качестве Observable<TagData[]> .

Я думаю, что что-то подобное сработает для вас:

 tagData$ = this.pageService.getTagsOnPage(this.workspaceId, this.currentPage.id).pipe(
    switchMap(references => from(references)),
    mergeMap(ref => this.afs.doc(ref).snapshotChanges()),      
    map(actions => actions.payload.data()),
    scan((allTagData, tagData) => [...allTagData, tagData], [])
  })
);
  

Давайте разберем это!

  1. Мы используем from для создания наблюдаемого из вашего массива, который выдает каждую «ссылку» по одной за раз.

  2. mergeMap подпишется на создаваемый нами observable и выполнит его эмиссии

  3. map просто преобразует значение в желаемую форму

  4. scan накапливает ваши эмиссии для вас и генерирует при каждом изменении. если вы не хотите отправлять, пока не вернутся все вызовы, используйте reduce() вместо этого

Теперь вы можете просто выполнить: tagData$.subscribe() и делать то, что вы хотите с полученным массивом данных. Или вы могли бы даже использовать асинхронный канал в вашем шаблоне вместо подписки в вашем компоненте.

Комментарии:

1. Блестяще! Спасибо за этот ответ! Способ его настройки придется tagData редактировать как отдельный массив (это просто для получения значений во время открытия компонента), но это намного лучше, чем то, что мы придумали (целая куча дополнений).