James B
James B

Reputation: 9605

Buffer grouped observables

I have the following class and array

class Hero {
    id: number;
    name: string;
}

const HEROES: Hero[] = [
    { id: 11, name: 'Narco' },
    { id: 12, name: 'Narco' },
    { id: 13, name: 'Bombasto' },
    { id: 14, name: 'Bombasto' },
    { id: 15, name: 'Bombasto' },
    { id: 16, name: 'Dynama' },
    { id: 17, name: 'Dynama' },
    { id: 18, name: 'Dynama' },
    { id: 19, name: 'Dynama' },
    { id: 20, name: 'Dynama' }
];

I want to create an observable that treats the HEROES array as the source, groups the array by name and emits the result as a single array, i.e., I should end up with three arrays, one for Narco, one for Bombasto and one for Dynama.

I can create my source observable as follows

var heroSource = Observable.create((observer:any) => {
            HEROES.forEach((hero: Hero) => {
                observer.next(hero);
            })
        });

I can then group heros by using groupBy, i.e.,

var groupHeroSource = heroSource
            .groupBy((hero: Hero) => {return hero.name});

This effectively gives me as many observables as there are different names in my HEROES array (three in this case). However, these will be emitted as a sequence and ideally I'd like to buffer them until heroSource is complete. How would I use the buffer operator in rxjs on my grouped observables so that emit a single collection?

Upvotes: 0

Views: 404

Answers (1)

Meir
Meir

Reputation: 14395

First of all you can create your initial observable much simpler:

var heroSource = Observable.from(HEROES);

Next, your groupBy mapper/selector can be abbreviated to:

var groupHeroSource = heroSource.groupBy((hero: Hero): string => hero.name);

To solve the original problem I needed to buffer the streams, since they are ready a buffer with time of 0 would do the work (I guess there should be a more elegant solution out there), use take(1) to take only the first result (and avoid a repeating buffer) and then merge all:

var finalGroup = groupHeroSource.map((ob) => ob.bufferWithTime(0).take(1)).mergeAll();

Note that since that since your array is actually static, putting it through a stream and then mapping it might not be the simplest solution, you can simply reduce it:

var grouped = HEROES.reduce((acc: any, hero: Hero) => {
  acc[hero.name] = acc[hero.name] || [];
  acc[hero.name].push(hero);
  return acc;
}, {});

Since Object.values is not standard, you'll have to iterate the keys to get an array, yet, it might be a better fit for your need

Upvotes: 4

Related Questions