Wilt
Wilt

Reputation: 44326

Flatten a parent child structure to a BehaviorSubject that resolves to an array

I have a BehaviourSubject that resolves into a collection where each item of has a property children that also is a BehaviourSubject resolving into a collection of items. Now I want to subscribe to my subject and flatten the whole thing into an array where I have a row for each item and each child.

So something in the line of:

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

Now I want to make another behavior subject out of this that resolves into an array where I have an item for each parent followed by its children.

Starting with:

const collection: BehaviorSubject<Parent[]>;

Want to result to:

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

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

So let's say the first Parent in the collection has 3 children, and second has 2 children then if I subscribe to the resulting behaviourSubject the output should look like this.

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

How do I achieve this.

I tried to figure out how to do this, by reading in the documentation of RxJS and checking examples, but I guess the recent lack of sleep seems to make my brain work like a dry sponge and I thought the best right now would be to call out for some help on StackOverflow.

Note: if the parent collection or the children get a new value, the result should be updated as well...

Upvotes: 0

Views: 376

Answers (1)

Fan Cheung
Fan Cheung

Reputation: 11345

Something like below show work, but I wonder why you use BehaviorSubject to add up the complexity

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)

if you dont mind make use the .value property from BehaviourSubject the code can be reduced to below

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

EDIT (BY OP):

I will edit this question for those wondering about the different steps in this answer I broke it down in pieces.
It can also be found in this StackBlitz to play around with.

Constructing the data:

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'),
  ])),
]);

Here the final observable with its different operations in the pipes with comments added to explain what they exactly do when subscribing to the result:

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)

Upvotes: 1

Related Questions