【问题标题】:Flatten a parent child structure to a BehaviorSubject that resolves to an array将父子结构展平为解析为数组的 BehaviorSubject
【发布时间】:2020-03-10 09:02:01
【问题描述】:

我有一个BehaviourSubject,它解析为一个集合,其中每个项目都有一个属性children,它也是一个BehaviourSubject,解析为一个项目集合。 现在我想订阅我的主题并将整个内容展平为一个数组,其中每个项目和每个孩子都有一行。

所以有些东西是:

interface Parent = {
  children: BehaviorSubject<Child[]>
}

现在我想以此作为另一个行为主题,它解析为一个数组,其中我为每个父项都有一个项目,然后是其子项。

开始于:

const collection: BehaviorSubject<Parent[]>;

想要结果:

const behaviourSubject: BehaviorSubject<(Parent|Child)[]>;

behaviourSubject.subscribe((result: (Parent|Child)[]) => { 
  ...desired result... 
});

假设集合中的第一个 Parent 有 3 个孩子,第二个有 2 个孩子,那么如果我订阅生成的 behaviourSubject,输出应该如下所示。

result: [Parent, Child, Child, Child, Parent, Child, Child];

我如何做到这一点。

我试图通过阅读 RxJS 的文档和检查示例来弄清楚如何做到这一点,但我想最近缺乏睡眠似乎让我的大脑像干海绵一样工作,我认为现在最好的方法是在 StackOverflow 上寻求帮助。

注意:如果父集合或子集合获得新值,则结果也应更新...

【问题讨论】:

  • 你也要把包含子元素的父元素放入数组中吗?
  • @FanCheung 是的,我需要数组中的父母和孩子按各自的顺序排列。

标签: typescript rxjs flatten behaviorsubject


【解决方案1】:

类似下面的东西展示了工作,但我想知道你为什么使用 BehaviorSubject 来增加复杂性

import { first, switchMap, toArray, map } from 'rxjs/operators';
import { pipe, BehaviorSubject, from } from 'rxjs';

//...

const parent1={children:new BehaviorSubject([{name:'john'},{name:'may'},{name:'betty'}])}

const parent2={children:new BehaviorSubject([{name:'key'},{name:'tom'}])}

const collection=new BehaviorSubject([parent1,parent2]);

collection.pipe(
  first(),
  switchMap(parent=>from(parent)),
  switchMap(p=>p.children.pipe(first(),map(c=>[p,...c]))),
  toArray(),
  map(arr=>arr.flat())
).subscribe(console.log)

如果您不介意使用 BehaviourSubject 中的 .value 属性,代码可以简化为以下

collection.value.map(p=>[p,...p.children.value]).flat()

编辑(由 OP):

我将为那些想知道此答案中不同步骤的人编辑此问题,我将其分解为几部分。
也可以在 this StackBlitz 中找到它来玩弄。

构建数据:

import { first, switchMap, toArray, map } from 'rxjs/operators';
import { pipe, BehaviorSubject, from } from 'rxjs';

class Child {
  constructor(public name: string){
  }
}

class Parent {
  constructor(public children: BehaviorSubject<Child[]>){
  }
}

const collection = new BehaviorSubject([
  new Parent(new BehaviorSubject([
    new Child('john'),
    new Child('may'),
    new Child('betty'),
  ])),
  new Parent(new BehaviorSubject([
    new Child('key'),
    new Child('tom'),
  ])),
]);

这里是最终的 observable,它在管道中添加了 cmets 以解释它们在订阅结果时的确切作用:

collection.pipe(
  //Emit the first value 
  first(),
  // Complete previous inner observable, emit values
  switchMap((parents: Parent[]) => { 
    // Emit array as a sequence of values
    return from(parents)
  }),
  // complete previous inner observable, emit values
  switchMap((parent: Parent) => { 
    return parent.children.pipe(
      // Emit the first value 
      first(),
      // Apply projection with each value from source
      map(
        (children: Child[]) => {
          // return desired array for each parent
          return [parent, ...children];
        }
      )
    )
  }),
  //Collects all source emissions and emits them as an array when the source completes
  toArray(),
  //map(arr=>arr.flat())
  map((array: (Parent|Child)[][] ) => {
    // Flatten the array
    return array.flat();
  }),
).subscribe(console.log)

【讨论】:

  • Hej,感谢您的回答。我会检查这个解决方案。我使用BehaviorSubject,因为它们反映了可以通过一些实时服务逻辑接收新值(新项目和更改的属性)的对象。像这样,当新数据到达时,我的视图会自动更新。这些嵌套对象反映了其他端点(关系数据)。但是,如果您发现有可能在不使用 BehaviorSubject 的情况下简化它,同时保持实时更改内容,我当然愿意接受建议!
  • 如果行为主体不断发出你将无法将结果减少到一个数组,但你可以使用扫描运算符发出最新的累积数组
  • 成功了,谢谢!我在答案中添加了所需的导入语句。我不小心从另一个位置导入了switchMap,导致一些意外错误;它将在未来帮助我和其他人。
猜你喜欢
  • 1970-01-01
  • 2019-02-17
  • 2018-05-16
  • 1970-01-01
  • 1970-01-01
  • 2013-03-27
  • 1970-01-01
  • 2015-02-01
  • 2021-03-13
相关资源
最近更新 更多