使用Angularfire2和firestore加入查询

时间:2018-05-15 09:07:13

标签: javascript angular google-cloud-firestore angularfire2

感谢阅读。

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

我有一系列“客户”和一系列“工作”。客户可以拥有多个工作,每个工作都有一个链接客户。

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

这是我的数据:

Client list

Jobs list

我已经能够“破解”一个解决方案,但它非常错误,并且它失去了所有异步 - 所以我不妨写一个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数据库中那样进行“加入”以将这两个数据集合在一起的方法?是否有一种方法可以保持数据的异步性?

3 个答案:

答案 0 :(得分:1)

寻找解决方案,但是经过四个小时的搜索,我发现这个解决方案库对于联接收集和获取数据非常有用。

主要资源: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. “客户” =这是我们加入的集合名称。

现在最后一步显示结果

 <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>

最后,此代码给出了两个收集记录。 谢谢。

答案 1 :(得分:1)

在尝试多种解决方案后,我使用 RXJS combineLatest, take 运算符完成了它。使用 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');
    },

答案 2 :(得分:0)

您可以尝试使用Promise.all()将多个承诺提取到一个对象中。以下是您的方法的修订版本,如果我正确理解您的问题,则应该提供所需的结果:

  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()接受了一个promises数组,并将所有这些变量解析为一个数组,作为{{1}中的get()方法是一个承诺。这也是使用async / await。希望这可以有所帮助!

编辑:

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

在您的组件中,您将要导入firestore模块并使用它们提供的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

这样,一旦firestore拥有数据,您的组件将监听新数据,并且它将一次全部进入,因此您没有易于创建多个订阅的嵌套订阅。希望这可以有所帮助。