【问题标题】:Inner subscribe value needed inside forEach of an outer subscription外部订阅的 forEach 中需要内部订阅值
【发布时间】:2021-08-15 05:43:21
【问题描述】:

我有一个 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 的 Receipts,但我不想在表中显示 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 中,dataSource 中的所有内容都正确显示,但 user_name 未定义。如果我在第二个订阅中console.log(user_name),它会正确显示。但是我看到第二个订阅中的console.log(user_name) 在我创建的对象“receipt”被推入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 的任何想法?

【问题讨论】:

  • @Eliseo 非常感谢您的回答。不幸的是,我调整了我项目的所有答案都无济于事,他们都没有得到任何数据供我在表格中显示。
  • 我写了一个答案。我指出的链接确实有一些不同,我希望 cmets 帮助您理解代码。注意:不要担心 rxjs 运算符。在这个例子中你最需要的是:switchMap、forkJoin 和 map(其实还有很多,但最常用的只有这三个)

标签: angular firebase firebase-realtime-database


【解决方案1】:

当您需要对外部 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

【讨论】:

  • 我尝试将您的示例集成到我的项目中。不幸的是,mergeMap(receipt => addUsername(receipt)) 没有将收据传递给 addUsername 函数,而是传递收据数组。我可以使用receipt[0].payload.val() 访问addUsername 中的数组,例如收据数组中的第一个收据。所以我认为 mergeMap 不能正常工作?
  • 我现在意识到我认为 mergeMap 不起作用,因为 getReceiptsByStoreId 返回一个长度为 0 但里面有收据的收据数组。
  • 我认为您需要首先找出您的 Firebase 查询未返回您期望的数据的原因。首先以您期望的方式获取您期望的数据,然后再担心其余部分。
【解决方案2】:

在这种情况下比指示的要复杂一些,因为我们不想拨打这么多电话来获取数据用户,否则只需要与我们得到的不同用户一样多的电话

我写了几个 cmets。步骤是

  1. 调用以获取所有“ReceptsByStoreId”
  2. 使用 uniq user_Id 获取数组
  3. 创建一个 observable 数组并使用 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,我写了一个空数组,函数,添加element.user_id如果不在valueAnterior中(添加元素我使用“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;
  }))
}

我们将更改您的服务以返回“收据”数组和整个“用户”。最好改造使用该组件的服务。这允许一个更“可扩展”的应用程序 - 想象一下您决定更改数据库,最好更改一个服务几个组件

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是如何工作的-)

【讨论】:

  • getReceiptsByStoreId 上,我可以通过调用forEach (element) 并将element.payload.val() 添加到本地数组,然后调用reduce 来从res:any[] 检索实际值一。之后,在执行forkJoin 时,users:any[] 为空,所以我认为getUserNameByUserId 方法没有按预期工作。 getUserNameByUserId 确实检索了 Firebase 对象,它返回了一个 DataSnapshot,我必须在其上调用 payload.val() 才能查看实际值。但它是空的,所以我无法从 users:any[] 获得任何值。
  • 另外,在console.log(obs) 上,我在控制台中看到了一个显示数组长度为 0 的可观察数组,但其中包含项目!我认为因为它稍后会填充,这就是 users:any[] 为空的原因。
  • @keesiah,我真的不了解 FireBase,我想问题是服务不返回“recepit”数组和“整个用户”对象。我认为更好的选择是在服务中使用pipe(map) 来返回所需的对象。我更新了答案,但要小心,我永远不会使用 FireBase,所以答案可能有很多错误(道歉)
猜你喜欢
  • 2020-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-23
  • 1970-01-01
  • 1970-01-01
  • 2020-10-11
  • 2019-09-07
相关资源
最近更新 更多