外部订阅中需要的内部订阅值

时间:2021-05-26 23:58:36

标签: angular firebase firebase-realtime-database

我有一个包含以下内容的 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,我应该采用什么方法?

2 个答案:

答案 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)

在这种情况下,所指示的要复杂一些,因为我们不想进行如此多的调用来获取数据用户,否则只会调用与我们获取的不同用户一样多的调用

我写了几条评论。步骤是

  1. 调用以获取所有“ReceptsByStoreId”
  2. 获取一个带有 uniq user_Id 的数组
  3. 创建一组 observables 并使用 forkJoin
  4. 使用 map 返回 ReceptsByStore 将属性 user_id 替换为 用户名

  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的工作原理-)