【问题标题】:Join query with Angularfire2 and firestore使用 Angularfire2 和 firestore 加入查询
【发布时间】:2018-10-25 01:21:57
【问题描述】:

感谢阅读。

我在 Firestore 中有两个集合,我正在使用 Angularfire2。

我有一个“客户”集合和一个“工作”集合。客户端可以有多个作业,每个作业都有一个链接的客户端。

我创建了一个组件来显示所有个作业的列表,我正在尝试使用 Firestore 键为每个作业拉入关联的客户端。

这是我的数据:

我已经能够“破解”一个解决方案,但它非常有问题,而且它失去了所有异步功能 - 所以我不妨编写一个 MySQL 后端并忘记 Firestore - 使用 Firestore 的全部意义在于它是“实时的”。

    public jobs = {};
[...]       
    processData() {

            const clients = [];
             this.clientCollection.ref.get().then((results) => {
              results.forEach((doc) => {
                clients[doc.id] = doc.data();
              });
              const jobs = {};
              this.jobsCollection.ref.get().then((docSnaps) => {
                docSnaps.forEach((doc) => {
                  jobs[doc.id] = doc.data();
                  jobs[doc.id].id = doc.id;
                  jobs[doc.id].clientData = clients[doc.data().client];
                });
                this.jobs = jobs;
              });

            });

          }

这在一定程度上有效 - 但它消除了异步。

关键问题:有什么方法可以像在 SQL 数据库中那样进行“连接”以将这两个数据集拉到一起?有没有一种方法可以保持数据的异步性?

【问题讨论】:

  • 您好,您有解决方案吗?我遇到了与您完全相同的问题,我尝试使用 Observable.combineLatest() 如下答案所述,但不是我想要的。

标签: javascript angular google-cloud-firestore angularfire2


【解决方案1】:

您可以尝试使用 Promise.all() 将多个 Promise 提取到一个对象中。这是您的方法的修订版本,如果我正确理解您的问题,应该会给出预期的结果:

  async processData() {
    const clientCollection = await firebase.firestore().collection(`clients`).get();
    const jobsCollection = await firebase.firestore().collection(`jobs`).get();
    const clients = [];
    clients.push(clientCollection);
    clients.push(jobsCollection);
    const res = await Promise.all(clients);
    // res should equal a combination of both querySnapshots
  }

我刚刚重新创建了集合变量以显示要添加到数组中的内容,但Promise.all() 接受一组承诺并将它们全部解析为一个数组,因为firestore 中的get() 方法是一个承诺。这也是使用 async/await。希望对您有所帮助!

编辑:

由于您使用的是 AngularFire2,因此您应该使用他们的方法。

在您的组件中,您将要导入 angularfire2/firestore 模块并使用它们提供的 Observable 方法:

首先导入模块:import { AngularFireStore } from 'angularfire2/firestore';

然后将其提供给您的构造函数:

  constructor(
    private firestore: AngularFireStore
  ) { }

那么你可以使用Observable.combineLatest()一次性接收所有数据:

  clients$: Observable<any[]>;
  clients: any[];

  jobs$: Observable<any[]>;
  jobs: any;

  joined: any;

  ngOnInit() {
    this.clients$ = this.getCollection('clients');
    this.jobs$ = this.getCollection('jobs');
    this.joined = Observable.combineLatest(this.clients$, this.jobs$);
    this.joined.subscribe(([clients, jobs]) => {
      this.clients = clients;
      this.jobs = jobs;
    });
  }


  getCollection(collectionName) {
    return this.firestore.collection(`${collectionName}`).valueChanges();
  }

在您的标记中,您只需使用 *ngFor 循环数据:

&lt;div *ngFor="let client of clients"&gt;{{ client.name }}&lt;/div&gt;

这样,您的组件将在 firestore 获得数据后侦听新数据,并且这些数据将同时进入,因此您不会有容易创建多个订阅的嵌套订阅。希望这能有所帮助。

【讨论】:

  • 在这里放纵我...我将如何使用此方法访问模板中的数据?
  • 嘿,我刚刚对可能简化此问题的答案进行了编辑。
  • 我看不出你在哪里将客户和工作联系起来。
【解决方案2】:

迟到的解决方案,但经过 4 小时的搜索,我发现这个解决方案库对于加入收集和获取数据非常有用。

主要资源:https://github.com/AngularFirebase/133-firestore-joins-custom-rx-operators

首先创建一个类型脚本文件

文件名:collectionjoin.ts

import { combineLatest, pipe, of, defer } from 'rxjs';
import { map, switchMap, tap } from 'rxjs/operators';



  export const leftJoinDocument = (afs: AngularFirestore, field, collection) => {
  return source =>
    defer(() => {
      // Operator state
      let collectionData;
      const cache = new Map();

      return source.pipe(
        switchMap(data => {
          // Clear mapping on each emitted val ;
          cache.clear();

          // Save the parent data state
          collectionData = data as any[];

          const reads$ = [];
          let i = 0;
          for (const doc of collectionData) {
            // Skip if doc field does not exist or is already in cache
            if (!doc[field] || cache.get(doc[field])) {
              continue;
            }

            // Push doc read to Array
            reads$.push(
              afs
                .collection(collection)
                .doc(doc[field])
                .valueChanges()
            );
            cache.set(doc[field], i);
            i++;
          }

          return reads$.length ? combineLatest(reads$) : of([]);
        }),
        map(joins => {
          return collectionData.map((v, i) => {
            const joinIdx = cache.get(v[field]);
            return { ...v, [field]: joins[joinIdx] || null };
          });
        }),
        tap(final =>
          console.log(
            `Queried ${(final as any).length}, Joined ${cache.size} docs`
          )
        )
      );
    });
};

之后在您的 homepage.ts 或任何页面打字稿文件中。

     import {
        AngularFirestore,
        AngularFirestoreCollection,
        AngularFirestoreDocument,
        } from "@angular/fire/firestore";
    
    jobs: any
    constructor( public afStore: AngularFirestore ) { }
    
        
        this.afStore.collection('Jobs').valueChanges().pipe(
            leftJoinDocument(this.afStore, 'client', 'Clients'),
            shareReplay(1)
            ).subscribe((response) => {
                this.products = response;
        })

在这一步中,我们传递三个参数

  1. this.afStore = 这是 lib 的对象。
  2. 'client' = 这是作业集合的键/ID
  3. 'Clients' = 这是我们加入的集合名称。

现在最后一步显示结果

 <ion-list lines="full" *ngFor="let job of Jobs">
        <ion-item button  detail>             
          <ion-label class="ion-text-wrap">{{ job.Name }}</ion-label>
          <p>{{ job.Clients.companyName}}</p>
        </ion-item>
      </ion-list>

最后这段代码给出了两条集合记录。 谢谢。

【讨论】:

    【解决方案3】:

    在尝试了多种解决方案后,我使用 RXJS combineLatest 完成了它,取操作员。使用 map 函数我们可以组合结果。

    可能不是最佳解决方案,但它可以解决您的问题。

    combineLatest(
        this.firestore.collection('Collection1').snapshotChanges(),
        this.firestore.collection('Collection2').snapshotChanges(),
        //In collection 2 we have document with reference id of collection 1
    )
    .pipe(
        take(1),
    ).subscribe(
        ([dataFromCollection1, dataFromCollection2]) => {
    
            this.dataofCollection1 = dataFromCollection1.map((data) => {
                return {
                    id: data.payload.doc.id,
                    ...data.payload.doc.data() as {},
                }
                as IdataFromCollection1;
            });
    
            this.dataofCollection2 = dataFromCollection2.map((data2) => {
                return {
                    id: data2.payload.doc.id,
                    ...data2.payload.doc.data() as {},
                }
                as IdataFromCollection2;
            });
    
            console.log(this.dataofCollection2, 'all feeess');
    
            const mergeDataFromCollection =
                this.dataofCollection1.map(itm => ({
                    payment: [this.dataofCollection2.find((item) => (item.RefId === itm.id))],
                    ...itm
                }))
    
            console.log(mergeDataFromCollection, 'all data');
        },
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-20
      • 2017-06-05
      • 2018-07-07
      • 1970-01-01
      • 2023-04-01
      • 2019-01-16
      • 2018-06-27
      • 1970-01-01
      相关资源
      最近更新 更多