我有一个包含以下内容的 Firebase 实时数据库:
"user":
1: {
"id": 1,
"name": "Jason"
}
"receipts":
1: {
"id": 1,
"store_id": 1,
"user_id": 1,
"product": "apples"
},
2: {
"id" : 2,
"store_id": 2,
"user_id": 1,
"product": "oranges"
}
我简化了它,但它具有与此类似的结构。我试图在 Angular 的 MatTable 中使用来自 AngularFireDatabase equalTo("given store_id") 的查询在 mat-table 中列出具有给定 store_id 的收据,但我不想在表中显示 user_id,我想显示我可以通过使用 user_id 查询“user”来获取名称的 user_name。
我正在做这样的事情:
receiptDetails = new MatTableDataSource();
...
this.storeService.getReceiptsByStoreId(store_id).subscribe(result => {
let tempArray:any = []
result.forEach((element) => {
let object:any = element.payload.val()
let user_name:any;
this.storeService.getUserNameByUserId(object.user_id).subscribe(result => user_name = result)
let receipt:any = {
id: object.id,
product: object.product,
user_id: user_name
}
tempArray.push(receipt)
});
this.receiptDetails.data = tempArray
})
在 .html 中,数据源中的所有内容都正确显示,但未定义的 user_name
。如果 i console.log(user_name)
在第二个订阅中,它会正确显示。但是我看到第二个订阅中的 console.log(user_name)
在我创建的对象“收据”被推入 tempArray
之后执行,因此当它被推入数组时它是未定义的。
我使用的 storeService 中的方法如下所示:
public getReceiptsByStoreId(store_id: number) {
return this.db.list('/receipts', ref => ref.orderByChild('store_id').equalTo(store_id)).snapshotChanges()
}
public getUserNameByUserId(user_id: number) {
return this.db.object('/user/'+ user_id).snapshotChanges().pipe(map(user=>{
let object:any=user.payload.val();
let name=object.name;
return name;
}))
}
我认为 mergeMap 在这里不起作用,我已经尝试过了,但没有成功。为了不在表中显示 user_id 而是查询提供的 user_name,我应该采用什么方法?
答案 0 :(得分:0)
当您需要对外部 observable 的结果执行 foreach 并在 foreach 内部使用内部 observable 时,这通常是 mergemap 的一个很好的用例。
您可以尝试以下操作:
this.storeService.getReceiptsByStoreId(store_id)
.pipe(
mergemap(receipt => addUsername(receipt))
)
.subscribe(val => this.receiptDetails.data = val)
const addUsername = (receipt) => {
return this.storeService.getUserNameByUserId(receipt.user_id)
.pipe(
map(username => { id: receipt.id, product: receipt.product, user_id: username })
)
}
参见此处的示例 - https://stackblitz.com/edit/rxjs-gxktnh?file=index.ts
答案 1 :(得分:0)
在这种情况下,所指示的要复杂一些,因为我们不想进行如此多的调用来获取数据用户,否则只会调用与我们获取的不同用户一样多的调用
我写了几条评论。步骤是
ngOnInit()
{
const result$=this.dataService.getReceiptsByStoreId(1).pipe(
switchMap((res:any[])=>{
//in res we has the getReceiptsByStore
//we whan an unique users
//I use reduce, you can use others ways
const uniqUsers:number[]=res.reduce((a:number[],b:any)=>a.indexOf(b.user_id)<0?[...a,b.user_id]:a,[])
//uniqUsersnumber is an array with the name of the users
//we create an array of observables
const obs=uniqUsers.map(x=>this.dataService.getUserNameByUserId(x))
return forkJoin(obs).pipe(map((users:any[])=>{
//en users We has all the users "implicated"
//but we are going to return the getReceiptsByStore
//replacing the property user_id by the name of the user
return res.map(x=>({
...x,
user_id:users.find(u=>u.id==x.user_id).name
}))
}))
})
)
//we can use
this.dataSource=result$
//or we can subscribe
// result$.subscribe(res=>{
this.dataSource=res
})
}
在 this stackblitz 中,我使用服务中的“of”模拟对 API 的调用
代码注意事项 1.-reduce 函数获取唯一值,reduce 是方法
myarray.reduce(valueAnterior,elementOf theArray,function(),valueInitial)
作为valueInitial,我写了一个空数组,函数,如果不是在valueAnterior中添加element.user_id(添加元素我使用“spread”运算符
2.-当返回 res 时,我们返回 res.map - 是一个数组映射,而不是 rxjs- 并返回一个具有 res 每个元素的所有属性的对象,但我通过名称更改了 user_id 的值用户。我们也可以创建一个新变量
注意:我想象了一个以确定的方式返回数据的服务。我们可以使用 console.log(res)
来了解您收到了什么。我写的代码返回和对象数组。有可能我们收到了一个像
{data:[here-the array]}
所以代码不起作用。然后我们可以像
一样改变我们的代码 const result$=this.dataService.getReceiptsByStoreId(1).pipe(
switchMap((res:any)=>{
//see that we use res.data
const uniqUsers:number[]=res.data.reduce(...)
....
)
或者我们可以更改从我们的服务中接收到的数据。对我来说是最好的选择,因为想象一下,如果您更改了 dbs 并且知道不是 Firebase Realtime 也不是带有 .NET 中的 API 的 MySQL,那么您唯一需要更改的是服务,而不是组件。所以
更新 ::glups:: 您使用的是 FireBase,因此如果您进行简单调用,您不会收到数组
我真的不是 FireBase 的专家,但阅读教程,你有两种服务
public getReceiptsByStoreId(store_id: number) {
return this.db.list('/receipts', ref => ref.orderByChild('store_id').equalTo(store_id)).snapshotChanges()
}
public getUserNameByUserId(user_id: number) {
return this.db.object('/user/'+ user_id).snapshotChanges().pipe(map(user=>{
let object:any=user.payload.val();
let name=object.name;
return name;
}))
}
我们将更改您的服务以返回和“收据”数组和整个“用户”。最好对使用组件的服务进行转换。这允许更“可扩展”的应用程序 - 假设您决定更改 dbs,最好更改多个组件的一项服务
public getReceiptsByStoreId2(id: number) {
return this.db.list('/receipts', ref => ref.orderByChild('store_id').equalTo(store_id)).snapshotChanges()
.pipe(
map((recepSnapshot: any[]) => {
return recepSnapshot.map((recepData: any) => {
const data = recepData.payload.val();
return {
id: recepData.payload.key,
...data
};
});
})
);
}
和
public getUserNameByUserId(user_id: number) {
return this.db.object('/user/'+ user_id).snapshotChanges().pipe(map(user=>{
return user.payload.val(); //<--return the whole user
}))
}
(*)免责声明,我不知道fireBase(我只看了一个简单的教程,我没有检查代码-我只希望代码有助于理解FireBase的工作原理-)