感谢阅读。
我在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数据库中那样进行“加入”以将这两个数据集合在一起的方法?是否有一种方法可以保持数据的异步性?
答案 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;
})
在这一步中,我们将传递三个参数
现在最后一步显示结果
<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拥有数据,您的组件将监听新数据,并且它将一次全部进入,因此您没有易于创建多个订阅的嵌套订阅。希望这可以有所帮助。