Используйте RxJs Pipe для уменьшения наблюдаемого до другого типа

У меня есть Observable<Recipe[]>, который я хочу уменьшить до массива другого класса ChartData[], чтобы использовать его в качестве источника данных для диаграммы highcharts (столбец и круговая диаграмма).

Я пытаюсь использовать оператор канала RxJS на Observable<Recipe[]>, чтобы вызвать оператор сокращения для моих данных, но я не могу заставить его работать? Оператор reduce не выполняет итерацию по этим элементам в моем Observable<Recipe[]> Ниже моя попытка:

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() возвращает Observable<Recipe[]>

this.chartData$ is Observable<ChartData[]>

Я пытаюсь уменьшить это до ChartData[]. Мне удалось сделать это с помощью оператора 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));
  });

Я пробовал использовать код subscribe в конвейере reduce, но получаю ошибки компиляции, в которых говорится, что метод ожидает Recipe[] для значения. Но если я использую массив, я получаю только первый элемент из Observable (или я просто получаю Observable и мне нужно что-то с этим делать?)

Возможно ли это, или мой мыслительный процесс неверен в отношении того, как конвейерный оператор должен работать с Observable?

Для справки приведены модели и функция createOrFindAuthor:

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;
}

person Simply Ged    schedule 03.05.2018    source источник


Ответы (3)


Итак, Чау Тран поставил меня в правильное русло. Очевидно, мне нужно было switchMap преобразовать Observable в Recipe[], и оператор reduce был рад принять Recipe в качестве значения. Решение следующее:

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();
person Simply Ged    schedule 03.05.2018

После reduce попробуйте:

switchMap(data => {
    this.chartData$ = of(data.sort((a, b) => b.y - a.y));
    return this.chartData$;
})
.subscribe()
person Chau Tran    schedule 03.05.2018
comment
Хотя это улучшает мой код, это не решает проблему с оператором reduce для перебора всех элементов в Observable. - person Simply Ged; 03.05.2018
comment
Вы меня правильно поставили, просто нужен был дополнительный switchMap :-) - person Simply Ged; 03.05.2018
comment
Ага, плохо, по какой-то причине я подумал, что вы уже получите Recipe[] в reduce. - person Chau Tran; 03.05.2018

Я создал этот пример stackblitz для демонстрации использования reduce(). В проекте есть и другие вещи, но все, что вам нужно, находится в demoReduce.ts:

import { Observable, of } from 'rxjs'
import { reduce, tap } from 'rxjs/operators'

type Book = {
  title: string
  noPages: number
}

type Library = {
  totalPages: number
  books: Book[]
}

export const demoReduce = () => {
  const books$: Observable<Book> = of(
    { title: 'book 1', noPages: 10 },
    { title: 'book 2', noPages: 20 },
    { title: 'book 3', noPages: 30 },
  )

  return books$.pipe(
    // --- reduce a stream of "Book" into a "Library"
    reduce<Book, Library>((previous, book) => {
      // --- add book to "Library" and increment totalPages in "Library"
      return {
        totalPages: previous.totalPages + book.noPages,
        books: [
          ...previous.books,
          book
        ]
      }
    }, { totalPages: 0, books: [] }),
    tap(val => console.log(val))
  )
}

Чтобы выполнить наблюдаемое, используйте кнопку «Demo Reduce» и посмотрите на консоль.

Он преобразует поток Books в один объект Library.

Примечание:

  • Я не уверен, почему stackblitz показывает ошибку (волнистое красное подчеркивание) в онлайн-редакторе для reduce(). Я не получал сообщения об ошибке в IntelliJ / WebStorm. Я подозреваю, что это ошибка stackblitz.

Обновлять:

Вот та же функция, которая принимает Observable<Book[]> в качестве входных данных (не проверена):

export const demoReduceWithArray = () => {
  const books$: Observable<Book[]> = of([
    { title: 'book 1', noPages: 10 },
    { title: 'book 2', noPages: 20 },
    { title: 'book 3', noPages: 30 }
  ])

  return books$.pipe(
    // --- reduce a stream of "Book[]" into a "Library"
    reduce<Book[], Library>((previous, books) => {
      // --- add each book to "Library" and increment totalPages in "Library"
      books.map(book => {
        previous = {
          totalPages: previous.totalPages + book.noPages,
          books: [
            ...previous.books,
            book
          ]
        }
      })
      return previous
    }, { totalPages: 0, books: [] }),
    tap(val => console.log(val))
  )
}
person kctang    schedule 03.05.2018
comment
Я только что понял, что вам нужен Observable ‹Recipe []› вместо Observable ‹Recipe› в качестве входных данных. Извините! В любом случае, я надеюсь, что демо может служить руководством для настройки. :-) - person kctang; 03.05.2018