Use RxJs Pipe to reduce Observable to different type

Simply Ged picture Simply Ged · May 3, 2018 · Viewed 9.5k times · Source

I have an Observable<Recipe[]> that I want to reduce to an array of a different class ChartData[] to use as a data source for a highcharts graph (column and piechart).

I am trying to use the RxJS pipe operator on the Observable<Recipe[]> to call the reduce operator on my data but I cannot get it to work? The reduce operator does not iterate over them items in my Observable<Recipe[]> Below is my attempt:

this.foodService.getAllReceipes()
  .pipe(
    reduce((array: ChartData[], value: Recipe[], i: number) => {
        const author = this.createOrFindAuthor(array, value[i]);
        author.y += 1;

        return array;
      }, new Array<ChartData>())
  )
  .subscribe(data => this.chartData$ = of(data.sort((a, b) => b.y - a.y)));
}

getAllRecipes() returns the Observable<Recipe[]>

this.chartData$ is Observable<ChartData[]>

I am trying to reduce this to ChartData[]. I have been able to do this in the subscribe operator and the graphs display the expected data but I thought I should be able to do it as a pipeable operator? Here is the reduce being done as part of the subscribe:

this.foodService.getAllReceipes()
  .subscribe((data) => {
    const list = data.reduce((arr: ChartData[], v: Recipe) => {
      const author = this.createOrFindAuthor(arr, v);
      author.y += 1;

      return arr;
    }, new Array<ChartData>());

    this.chartData$ = of(list.sort((a, b) => b.y - a.y));
  });

I have tried using the subscribe code within the pipeable reduce but I get compile errors saying that the method expects Recipe[] for the value. But if I use the array then I only get the first element from the Observable (or am I just getting the Observable and need to do something with that?)

Is this possible or is my thought process wrong about how the pipeable operator should work on the Observable?

For reference here are the models and the createOrFindAuthor function:

export class Recipe {

    public Title: string;
    public Author: string;
    public Source: string;
    public Page: number;
    public Link?: string;
}

export class ChartData {
    name: string;
    y: number;
}

private createOrFindAuthor(array: ChartData[], recipe: Recipe): ChartData {
  const name = (recipe.Author.length > 0 ? recipe.Author : 'UNKNOWN');

  let found = array.find(i => i.name === name);

  if (!found) {
    const newData = new ChartData();
    newData.name = name;
    newData.y = 0;
    array.push(newData);

    found = newData;
  }

  return found;
}

Answer

Simply Ged picture Simply Ged · May 3, 2018

So Chau Tran put me on the right lines. Apparently I needed to switchMap the Observable to a Recipe[] and the reduce operator was then happy to accept a Recipe as the value. Solution as follows:

this.foodService.getAllReceipes()
  .pipe(
    switchMap(data => data as Recipe[]),            <<== ADDED THIS

    reduce((array: ChartData[], value: Recipe) => {
        const author = this.createOrFindAuthor(array, value);
        author.y += 1;

        return array;
      }, new Array<ChartData>()),

      switchMap(data => this.chartData$ = of(data.sort((a, b) => b.y - a.y)))
  )
  .subscribe();